diff --git a/gemeinsamforschen/README.md b/gemeinsamforschen/README.md index 284556cdb9189584d471f938099cc7a57e3cbf28..8489d771e9d9a268d13930323750b5571cd75118 100644 --- a/gemeinsamforschen/README.md +++ b/gemeinsamforschen/README.md @@ -11,4 +11,18 @@ Go to `Administration -> Allgemeines -> REST API -> Enable Personal Access Token ### Manual Personal Access Token 1. Click on your profile picture -> `Mein Konto` -> `Personal Access Token` 1. add a new personal access token -1. add it to configuration class `GFRocketChatConfig` \ No newline at end of file +1. add it to configuration class `GFRocketChatConfig` + +### Enable iframe integration +Dont mix up with General/Allegemeines->IFRAME INTEGRATION + +Accounts/Konten->IFRAME: + +1. click on enable + +1. FOR API URL: http://141.89.53.195:8080/gemeinsamforschen/rest/chat/sso +localhost:8080/gemeinsamforschen/rest/chat/sso + +1. FOR IFRAME URL: http://141.89.53.195:8080/gemeinsamforschen/rest/chat/login +localhost:8080/gemeinsamforschen/rest/chat/login + diff --git a/gemeinsamforschen/pom.xml b/gemeinsamforschen/pom.xml index 5b2bd52a6a6a796f086ffb07f3dfdb57a86ebab2..16571efab215f165250dd42fc56e9d39530b4517 100644 --- a/gemeinsamforschen/pom.xml +++ b/gemeinsamforschen/pom.xml @@ -69,6 +69,13 @@ <version>5.1.6</version> </dependency> + <!-- converting html to java pojo --> + <!-- <dependency> + <groupId>fr.whimtrip</groupId> + <artifactId>whimtrip-ext-htmltopojo</artifactId> + <version>1.0.2</version> + </dependency>--> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/config/GFApplicationBinder.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/config/GFApplicationBinder.java index 196149177c7376f49eafd33f794a9de6be325e04..7f54357ce3fd7e7fda8c8577ba8d92d2562c5e24 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/config/GFApplicationBinder.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/config/GFApplicationBinder.java @@ -3,11 +3,11 @@ package unipotsdam.gf.config; import org.glassfish.hk2.utilities.binding.AbstractBinder; import unipotsdam.gf.interfaces.*; import unipotsdam.gf.modules.annotation.controller.AnnotationController; +import unipotsdam.gf.modules.annotation.controller.FeedbackImpl; import unipotsdam.gf.modules.assessment.controller.service.AssessmentDBCommunication; import unipotsdam.gf.modules.assessment.controller.service.PeerAssessment; import unipotsdam.gf.modules.communication.service.CommunicationService; import unipotsdam.gf.modules.communication.service.UnirestService; -import unipotsdam.gf.modules.feedback.FeedbackImpl; import unipotsdam.gf.modules.group.GroupDAO; import unipotsdam.gf.modules.group.GroupfindingImpl; import unipotsdam.gf.modules.journal.service.IJournalImpl; @@ -27,6 +27,7 @@ import unipotsdam.gf.process.ProjectCreationProcess; import unipotsdam.gf.process.constraints.ConstraintsImpl; import unipotsdam.gf.process.phases.PhasesImpl; import unipotsdam.gf.process.tasks.TaskDAO; +import unipotsdam.gf.session.GFContext; import unipotsdam.gf.session.GFContexts; public class GFApplicationBinder extends AbstractBinder { @@ -40,6 +41,7 @@ public class GFApplicationBinder extends AbstractBinder { bind(ManagementImpl.class).to(Management.class); bind(PeerAssessment.class).to(IPeerAssessment.class); bind(PhasesImpl.class).to(IPhases.class); + bind(GFContext.class).to(GFContext.class); bind(ManagementImpl.class).to(Management.class); bind(DummyResearchReportManagement.class).to(ResearchReportManagement.class); bind(IJournalImpl.class).to(IJournal.class); diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/config/GFRocketChatConfig.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/config/GFRocketChatConfig.java index 1f91fa18168d28f64d06cff755982ec3e70a1dbe..8fd0ea098cf755ec609feaa4f76069bf4f8bcddd 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/config/GFRocketChatConfig.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/config/GFRocketChatConfig.java @@ -1,16 +1,26 @@ package unipotsdam.gf.config; +import unipotsdam.gf.modules.communication.model.RocketChatUser; import unipotsdam.gf.modules.user.User; +/** + * ROCKET CHAT NEEDS TO KNOW THE ENDPOINT FOR THE SSO CONFIGURE LIKE IN README + * + * 1. FOR API URL: http://141.89.53.195:8080/gemeinsamforschen/rest/chat/sso + * localhost:8080/gemeinsamforschen/rest/chat/sso + * + * 1. FOR IFRAME URL: http://141.89.53.195:8080/gemeinsamforschen/rest/chat/login + * localhost:8080/gemeinsamforschen/rest/chat/login + */ public class GFRocketChatConfig { //private static final String ROCKET_CHAT_LINK = "http://rocketchat.westeurope.cloudapp.azure.com/"; + // public static final String ROCKET_CHAT_LINK_0 = "https://rocket.farm-test.rz.uni-potsdam.de"; + // public static final String ROCKET_CHAT_LINK = "https://rocket.farm-test.rz.uni-potsdam.de/"; - private static final String ROCKET_CHAT_LINK = "https://rocket.farm-test.rz.uni-potsdam.de/"; - - // or https://rocket.farm.uni-potsdam.de/ - // https://rocket.farm-test.rz.uni-potsdam.de/home + public static final String ROCKET_CHAT_LINK_0 = "http://fleckenroller.cs.uni-potsdam.de:3000"; + public static final String ROCKET_CHAT_LINK = "http://fleckenroller.cs.uni-potsdam.de:3000/"; /** * username: fltrailadmin pw: GEbCM1Rso6TUGGMKtGmg6c5EydMQEu61K9zdD10F @@ -20,14 +30,14 @@ public class GFRocketChatConfig { public static final String ROCKET_CHAT_API_LINK = ROCKET_CHAT_LINK + "api/v1/"; - public static final User TEST_USER = new User("test", "passwort", - "test@stuff.com", "test", "rocketChatAuthToken", - "rocketChatPersonalAccessToken", "LExqp3P3t6oQHhauH", false); + public static final RocketChatUser TEST_USER = new RocketChatUser("student1", "egal", + "student1@yolo.com", "student1", "", + "", "6ofqfp8J9ynfvspBJ", false); /* public static final User ADMIN_USER = new User("admin nachname", "passwort", "email", "rocketChatUsername", "rocketChatAuthToken", "rocketChatPersonalAccessToken", "rocketChatUserId", false);*/ - public static final User ADMIN_USER = new User("fltrailadmin", "GEbCM1Rso6TUGGMKtGmg6c5EydMQEu61K9zdD10F", + public static final RocketChatUser ADMIN_USER = new RocketChatUser("fltrailadmin", "GEbCM1Rso6TUGGMKtGmg6c5EydMQEu61K9zdD10F", "julian.dehne@uni-potsdam.de", "rocketChatUsername", "rocketChatAuthToken", "rocketChatPersonalAccessToken", "SuFbpF3P9aYEo634W", false); diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/MysqlDownException.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/MysqlDownException.java new file mode 100644 index 0000000000000000000000000000000000000000..647f39f6954cdad03233c8af372089ad7816d65a --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/MysqlDownException.java @@ -0,0 +1,4 @@ +package unipotsdam.gf.exceptions; + +public class MysqlDownException { +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/RocketChatDownException.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/RocketChatDownException.java new file mode 100644 index 0000000000000000000000000000000000000000..6b9bdb421f2ff7fa7ba214ebe78c6b7aff1c7398 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/RocketChatDownException.java @@ -0,0 +1,4 @@ +package unipotsdam.gf.exceptions; + +public class RocketChatDownException extends Exception { +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserDoesNotExistInMysqlException.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserDoesNotExistInMysqlException.java new file mode 100644 index 0000000000000000000000000000000000000000..5231c47a4ce7f4b3764a223a3730964dabc75e5e --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserDoesNotExistInMysqlException.java @@ -0,0 +1,4 @@ +package unipotsdam.gf.exceptions; + +public class UserDoesNotExistInMysqlException extends Exception { +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserDoesNotExistInRocketChatException.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserDoesNotExistInRocketChatException.java new file mode 100644 index 0000000000000000000000000000000000000000..a877a7f2ec4b0c6fed06392bcc6c40a53cab4372 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserDoesNotExistInRocketChatException.java @@ -0,0 +1,4 @@ +package unipotsdam.gf.exceptions; + +public class UserDoesNotExistInRocketChatException extends Exception { +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserExistsInMysqlException.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserExistsInMysqlException.java new file mode 100644 index 0000000000000000000000000000000000000000..f9982f918896a8397294fdbf7bc18ad8b8fb0f1d --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserExistsInMysqlException.java @@ -0,0 +1,9 @@ +package unipotsdam.gf.exceptions; + +public class UserExistsInMysqlException extends Exception { + + @Override + public String getMessage() { + return "Tried to create User but exists in mysql"; + } +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserExistsInRocketChatException.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserExistsInRocketChatException.java new file mode 100644 index 0000000000000000000000000000000000000000..8160dadf6cc03059fe1bb5d20ff447902a613b65 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/exceptions/UserExistsInRocketChatException.java @@ -0,0 +1,8 @@ +package unipotsdam.gf.exceptions; + +public class UserExistsInRocketChatException extends Exception { + @Override + public String getMessage() { + return "Tried to create User but exists in RocketChat"; + } +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/Feedback.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/Feedback.java index 647f8a2bdec41a7734e082be720c9b5e50bb0ca0..6430c0018ad7d8b4cf2678c09693134d13c5cc77 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/Feedback.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/Feedback.java @@ -1,8 +1,10 @@ package unipotsdam.gf.interfaces; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.modules.user.User; -import unipotsdam.gf.process.progress.HasProgress; import unipotsdam.gf.modules.researchreport.ResearchReport; +import unipotsdam.gf.process.tasks.Task; + +import java.util.List; /** @@ -19,8 +21,9 @@ public interface Feedback { /** * TODO implement: Assigns each student in a project a feedback target + * @param tasks */ - void assignFeedbackTasks(Project project); + void specifyFeedbackTasks(List<Task> tasks); /** * TODO implement: Get the research report you have to give feedback to diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IAnnotation.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IAnnotation.java index c65a363e991e6a483a4e8791ffd036f74c66918d..021265a13654c7998353dd51b459a242920d66be 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IAnnotation.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IAnnotation.java @@ -3,7 +3,7 @@ package unipotsdam.gf.interfaces; import unipotsdam.gf.modules.annotation.model.Annotation; import unipotsdam.gf.modules.annotation.model.AnnotationPatchRequest; import unipotsdam.gf.modules.annotation.model.AnnotationPostRequest; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import java.util.ArrayList; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/ICommunication.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/ICommunication.java index 357851caee4460dafc09c90a4028f50c535f74a3..4d1a3f7781d82e58d91d3ef9fe19c4aba9f7c939 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/ICommunication.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/ICommunication.java @@ -1,8 +1,12 @@ package unipotsdam.gf.interfaces; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; +import unipotsdam.gf.exceptions.UserExistsInRocketChatException; import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; import unipotsdam.gf.modules.communication.model.EMailMessage; +import unipotsdam.gf.modules.communication.model.RocketChatUser; import unipotsdam.gf.modules.communication.model.chat.ChatMessage; import unipotsdam.gf.modules.group.Group; import unipotsdam.gf.modules.project.Project; @@ -37,7 +41,8 @@ public interface ICommunication { * @param name chat room name * @return chat room id */ - String createChatRoom(String name, boolean readOnly, List<User> users); + String createChatRoom(String name, boolean readOnly, List<User> users) + throws RocketChatDownException, UserDoesNotExistInRocketChatException; /** * creates chatRoom with name "group.projectId - group.id" and set chatRoomId for group @@ -45,11 +50,17 @@ public interface ICommunication { * @param group Object for information * @return true if chatRoom was created, otherwise false */ - boolean createChatRoom(Group group, boolean readOnly); + boolean createChatRoom(Group group, boolean readOnly) + throws RocketChatDownException, UserDoesNotExistInRocketChatException; - String createEmptyChatRoom(String name, boolean readOnly); + String createEmptyChatRoom(String name, boolean readOnly) + throws RocketChatDownException, UserDoesNotExistInRocketChatException; - boolean deleteChatRoom(String roomId); + void deleteChatRoom(Group group) throws RocketChatDownException, UserDoesNotExistInRocketChatException; + + void deleteChatRoom(Project project) throws RocketChatDownException, UserDoesNotExistInRocketChatException; + + boolean deleteChatRoom(String roomId) throws RocketChatDownException, UserDoesNotExistInRocketChatException; /** * endpoint: https://rocket.chat/docs/developer-guides/rest-api/groups/invite/ @@ -58,9 +69,11 @@ public interface ICommunication { * @param user information about user * @return if user was added successfully */ - boolean addUserToChatRoom(User user, String roomId); + boolean addUserToChatRoom(User user, String roomId) + throws RocketChatDownException, UserDoesNotExistInRocketChatException; - boolean removeUserFromChatRoom(User user, String roomId); + boolean removeUserFromChatRoom(User user, String roomId) + throws RocketChatDownException, UserDoesNotExistInRocketChatException; /** * endpoint: https://rocket.chat/docs/developer-guides/rest-api/groups/settopic/ @@ -80,9 +93,9 @@ public interface ICommunication { * @param roomId chat room id * @return chat room information */ - String getChatRoomName(String roomId); + String getChatRoomName(String roomId) throws RocketChatDownException, UserDoesNotExistInRocketChatException; - boolean exists(String roomId); + boolean exists(String roomId) throws RocketChatDownException, UserDoesNotExistInRocketChatException; /** * api: https://rocket.chat/docs/developer-guides/rest-api/authentication/login/ @@ -90,7 +103,8 @@ public interface ICommunication { * @param user username and password * @return information about user, especially authtoken for later use of endpoints */ - boolean loginUser(User user); + RocketChatUser loginUser(User user) + throws RocketChatDownException, UserDoesNotExistInRocketChatException; /** * api 1: https://rocket.chat/docs/developer-guides/rest-api/users/register/ @@ -102,9 +116,13 @@ public interface ICommunication { * @param user registers user to rocket.chat * @return user id */ - boolean registerUser(User user); + boolean registerUser(User user) + throws RocketChatDownException, UserExistsInRocketChatException; + + String getChatRoomLink(String userEmail, String projectId) + throws RocketChatDownException, UserDoesNotExistInRocketChatException; - String getChatRoomLink(String userToken, String projectId); + String getProjectChatRoomLink(String projectName); // TODO implement as Email or whatever boolean sendSingleMessage(EMailMessage EMailMessage, User user); @@ -113,4 +131,8 @@ public interface ICommunication { boolean informAboutMissingTasks(Map<StudentIdentifier, ConstraintsMessages> tasks, Project project); boolean sendMessageToUsers(Project project, EMailMessage eMailMessage); + + public void delete(User user) throws RocketChatDownException, UserDoesNotExistInRocketChatException; + + } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IGroupFinding.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IGroupFinding.java index 2b1ee6f6aa1590c5d597afafcd4b1b258fdf8474..c76efc824ba40a36109ee20a9b0aa55308de4a2a 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IGroupFinding.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IGroupFinding.java @@ -1,5 +1,7 @@ package unipotsdam.gf.interfaces; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; import unipotsdam.gf.modules.group.Group; import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; import unipotsdam.gf.modules.project.Project; @@ -7,6 +9,7 @@ import unipotsdam.gf.modules.group.GroupFormationMechanism; import unipotsdam.gf.modules.group.GroupfindingCriteria; import java.util.ArrayList; +import java.util.List; public interface IGroupFinding { @@ -18,6 +21,8 @@ public interface IGroupFinding { void selectGroupfindingCriteria( GroupfindingCriteria groupfindingCriteria, Project project); + GroupFormationMechanism getGFM(Project project); + /** * Persist the selected manual groups * @param groupComposition @@ -45,4 +50,13 @@ public interface IGroupFinding { ArrayList<String> getStudentsInSameGroup(StudentIdentifier student); int getMinNumberOfStudentsNeeded(Project project); + + void deleteGroups(Project project); + + List<Group> createRandomGroups(Project project); + + /** + * finish the groups in the db + */ + void finalizeGroups(Project project) throws RocketChatDownException, UserDoesNotExistInRocketChatException; } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IPhases.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IPhases.java index a618c43f01312c2ac788665c6d34debe5ce72d0b..c39e84231a33b1afc72e9843d42fe773c6174974 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IPhases.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/IPhases.java @@ -1,5 +1,7 @@ package unipotsdam.gf.interfaces; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.process.phases.Phase; @@ -9,7 +11,7 @@ public interface IPhases { * @param phase the phase to end * @param project the project to end the phase in */ - void endPhase(Phase phase, Project project); + void endPhase(Phase phase, Project project) throws RocketChatDownException, UserDoesNotExistInRocketChatException; } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/ISubmission.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/ISubmission.java index f6ba14561ccc5764b353e30412537b5284f00b21..f0009798a0ee847d2e716b362b0c526b0c299bbb 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/ISubmission.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/interfaces/ISubmission.java @@ -1,6 +1,6 @@ package unipotsdam.gf.interfaces; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import unipotsdam.gf.modules.submission.model.FullSubmission; import unipotsdam.gf.modules.submission.model.FullSubmissionPostRequest; import unipotsdam.gf.modules.submission.model.SubmissionPart; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/controller/AnnotationController.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/controller/AnnotationController.java index 07af951808e76a44848707b262b4a49782f22dec..bdd94e4fb1826baa40308935337083c5bb9f0f64 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/controller/AnnotationController.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/controller/AnnotationController.java @@ -3,11 +3,13 @@ package unipotsdam.gf.modules.annotation.controller; import unipotsdam.gf.mysql.MysqlConnect; import unipotsdam.gf.mysql.VereinfachtesResultSet; import unipotsdam.gf.interfaces.IAnnotation; +import unipotsdam.gf.process.DossierCreationProcess; +import unipotsdam.gf.process.tasks.Task; import unipotsdam.gf.modules.annotation.model.Annotation; import unipotsdam.gf.modules.annotation.model.AnnotationBody; import unipotsdam.gf.modules.annotation.model.AnnotationPatchRequest; import unipotsdam.gf.modules.annotation.model.AnnotationPostRequest; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import javax.inject.Inject; import java.util.ArrayList; @@ -164,6 +166,15 @@ public class AnnotationController implements IAnnotation { } + public void endFeedback(Task task){ + connection.connect(); + String query = "UPDATE tasks set progress = ? where userEmail = ? AND projectName = ? AND taskName = ?"; + connection.issueUpdateStatement( + query, task.getProgress().name(), task.getUserEmail(), task.getProjectName(), task.getTaskName()); + connection.close(); + + } + /** * Build an annotation object from a given result set * diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/controller/FeedbackImpl.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/controller/FeedbackImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..1b00a913803b5087cb048012554963c47f5fbbb5 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/controller/FeedbackImpl.java @@ -0,0 +1,50 @@ +package unipotsdam.gf.modules.annotation.controller; + +import unipotsdam.gf.interfaces.Feedback; +import unipotsdam.gf.modules.project.Project; +import unipotsdam.gf.modules.researchreport.ResearchReport; +import unipotsdam.gf.modules.user.User; +import unipotsdam.gf.mysql.MysqlConnect; +import unipotsdam.gf.process.tasks.Task; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; + +public class FeedbackImpl implements Feedback { + + @Inject + MysqlConnect connection; + + @Override + public void assigningMissingFeedbackTasks(Project project) { + + } + + @Override + public void specifyFeedbackTasks(List<Task> tasks) { + for (Task task : tasks) { + List<String> studentsToFeedback = studentsToFeedback(tasks, task, 1); + for (String userEmail : studentsToFeedback) { + connection.connect(); + String request = "UPDATE `fullsubmissions` SET `feedbackUser`=? WHERE user=? AND projectName=?"; + connection.issueInsertOrDeleteStatement(request, userEmail, task.getUserEmail(), task.getProjectName()); + connection.close(); + } + } + } + + private List<String> studentsToFeedback(List<Task> tasks, Task task, int howMany) { + List<String> result = new ArrayList<>(); + int position = tasks.indexOf(task); + for (int i = 1; i <= howMany; i++) { + result.add(tasks.get((i + position) % tasks.size()).getUserEmail()); //modulo builds a circle in users + } + return result; + } + + @Override + public ResearchReport getFeedbackTask(User student) { + return null; + } +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/Annotation.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/Annotation.java index cdc7ed16bc834f01d842e0c46672f8e015c9a1c7..b7256cc6cb288123c5ad09dc022084be5aef699c 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/Annotation.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/Annotation.java @@ -1,7 +1,5 @@ package unipotsdam.gf.modules.annotation.model; -import unipotsdam.gf.modules.feedback.Category; - /** * @author Sven Kästle * skaestle@uni-potsdam.de diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/AnnotationMessage.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/AnnotationMessage.java index de3ba66c0df21d893cd951d7a42a25e27135e2c3..37cc3002d6c036d5ac3d1bf3f92c43300a408dfe 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/AnnotationMessage.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/AnnotationMessage.java @@ -1,7 +1,5 @@ package unipotsdam.gf.modules.annotation.model; -import unipotsdam.gf.modules.feedback.Category; - public class AnnotationMessage { // variables private String from; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/AnnotationPostRequest.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/AnnotationPostRequest.java index 61ebb981be6122bfd0e347eb9c2c2a6cd4022d98..8d2bfced663e9108b5801bfa2893b3deab3c76a3 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/AnnotationPostRequest.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/AnnotationPostRequest.java @@ -1,7 +1,5 @@ package unipotsdam.gf.modules.annotation.model; -import unipotsdam.gf.modules.feedback.Category; - /** * @author Sven Kästle * skaestle@uni-potsdam.de diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/Category.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/Category.java similarity index 82% rename from gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/Category.java rename to gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/Category.java index 67a3b26d51ac29d6bdfd69b2454a0fd560bfd039..323dcde82a26e7a59ff9ac1d260a6ade90f39fe6 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/Category.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/model/Category.java @@ -1,4 +1,4 @@ -package unipotsdam.gf.modules.feedback; +package unipotsdam.gf.modules.annotation.model; /** Categories for feedback and documents diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/view/AnnotationService.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/view/AnnotationService.java index 2119e428139a2072f165a49cd028c3fb2558e022..9d2a5723cfe5ca702849435295d965b72178b464 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/view/AnnotationService.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/view/AnnotationService.java @@ -6,9 +6,17 @@ import unipotsdam.gf.modules.annotation.model.Annotation; import unipotsdam.gf.modules.annotation.model.AnnotationPatchRequest; import unipotsdam.gf.modules.annotation.model.AnnotationPostRequest; import unipotsdam.gf.modules.annotation.model.AnnotationResponse; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; +import unipotsdam.gf.modules.project.Project; +import unipotsdam.gf.modules.project.ProjectDAO; +import unipotsdam.gf.process.DossierCreationProcess; +import unipotsdam.gf.process.tasks.Progress; +import unipotsdam.gf.process.tasks.Task; +import unipotsdam.gf.process.tasks.TaskName; +import unipotsdam.gf.session.GFContexts; import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -16,8 +24,11 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.ArrayList; /** @@ -33,6 +44,15 @@ public class AnnotationService { @Inject AnnotationController controller; + @Inject + private GFContexts gfContexts; + + @Inject + private ProjectDAO projectDAO; + + @Inject + private DossierCreationProcess dossierCreationProcess; + @POST public Response createAnnotation(AnnotationPostRequest request) { @@ -133,7 +153,24 @@ public class AnnotationService { return Response.status(Response.Status.NOT_FOUND).entity(response).build(); } - } + + @GET + @Path("/finalize/projectName/{projectName}/taskName/{taskName}") + @Produces("application/json") + public String finalizeFeedback(@Context HttpServletRequest req, @PathParam("projectName") String projectName, + @PathParam("taskName") String taskName) + throws UnsupportedEncodingException, IOException { + Task task= new Task(); + String userEmail = gfContexts.getUserEmail(req); + task.setProjectName(projectName); + task.setUserEmail(userEmail); + task.setTaskName(TaskName.valueOf(taskName)); + task.setProgress(Progress.FINISHED); + controller.endFeedback(task); + Project project = projectDAO.getProjectByName(projectName); + dossierCreationProcess.createCloseFeedBackPhaseTask(project); + return null; + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/websocket/AnnotationWSTarget.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/websocket/AnnotationWSTarget.java index 74fffe95960861bc8a19dc3491e3a460aaffe6d2..8c11de7cf55f3e46952f1a52863fd653b2c772b5 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/websocket/AnnotationWSTarget.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/websocket/AnnotationWSTarget.java @@ -1,6 +1,6 @@ package unipotsdam.gf.modules.annotation.websocket; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; /** * @author Sven Kästle diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/websocket/AnnotationWebSocketEndpoint.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/websocket/AnnotationWebSocketEndpoint.java index 6cb6f5799a199edf069a61c32f66cb95c02a9da6..743733131bd4cc1ab8838a1825e1ef7ead32fd1a 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/websocket/AnnotationWebSocketEndpoint.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/annotation/websocket/AnnotationWebSocketEndpoint.java @@ -1,7 +1,7 @@ package unipotsdam.gf.modules.annotation.websocket; import unipotsdam.gf.modules.annotation.model.AnnotationMessage; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import javax.websocket.EncodeException; import javax.websocket.OnClose; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/AssessmentMechanism.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/AssessmentMechanism.java index b3692469832c184c21d3f983fb6cd87202c96e05..f4c8f17e9044615b5699cd1cc6a3b55ce3c07ac9 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/AssessmentMechanism.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/AssessmentMechanism.java @@ -1,7 +1,7 @@ package unipotsdam.gf.modules.assessment; public enum AssessmentMechanism { - AXEL_MECHANISM, - CHRISTIAN_MECHANISM, - COMBINATIONXY + PEER_ASSESSMENT, + CO_ASSESSMENT, + COMBINATION } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/controller/service/AssessmentDBCommunication.java.orig b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/controller/service/AssessmentDBCommunication.java.orig deleted file mode 100644 index f0e3ec9a73fe9114f530f530310146db20fe6567..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/controller/service/AssessmentDBCommunication.java.orig +++ /dev/null @@ -1,352 +0,0 @@ -package unipotsdam.gf.modules.assessment.controller.service; - -import unipotsdam.gf.core.database.mysql.MysqlConnect; -import unipotsdam.gf.core.database.mysql.VereinfachtesResultSet; -import unipotsdam.gf.core.states.model.Constraints; -import unipotsdam.gf.core.states.model.ConstraintsMessages; -import unipotsdam.gf.modules.assessment.controller.model.Categories; -import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; -<<<<<<< HEAD -import unipotsdam.gf.modules.assessment.controller.model.cheatCheckerMethods; -======= -import unipotsdam.gf.modules.assessment.controller.model.Categories; -import unipotsdam.gf.modules.assessment.controller.model.Grading; -import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; ->>>>>>> origin/development_master - -import javax.annotation.ManagedBean; -import javax.annotation.Resource; -import javax.inject.Singleton; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@ManagedBean -@Resource -@Singleton -class AssessmentDBCommunication { - - cheatCheckerMethods getAssessmentMethod(String projectName){ - cheatCheckerMethods result = cheatCheckerMethods.none; - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `assessmentmethod` WHERE `projectName`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, projectName); - if (vereinfachtesResultSet.next()){ - String resultString = vereinfachtesResultSet.getString("cheatCheckerMethod"); - result = cheatCheckerMethods.valueOf(resultString); - } - return result; - } - - ArrayList<Map<String, Double>> getWorkRating(StudentIdentifier student) { - ArrayList<Map<String, Double>> result = new ArrayList<>(); - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `workrating` WHERE `projectName`=? AND `userName`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, student.getProjectId(), student.getStudentId()); - boolean next = vereinfachtesResultSet.next(); - while (next) { - Map<String, Double> workRating = new HashMap<>(); - for (String category : Categories.workRatingCategories) { - workRating.put(category, (double) vereinfachtesResultSet.getInt(category)); - } - result.add(workRating); - next = vereinfachtesResultSet.next(); - } - return result; - } - - Boolean getWorkRating(StudentIdentifier student, String fromStudent) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `workrating` WHERE `projectName`=? AND `userName`=? AND `fromPeer`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, student.getProjectId(), student.getStudentId(), fromStudent); - return vereinfachtesResultSet.next(); - } - - List<String> getStudents(String projectID) { - List<String> result = new ArrayList<>(); - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `projectuser` WHERE `projectName`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, projectID); - boolean next = vereinfachtesResultSet.next(); - while (next) { - result.add(vereinfachtesResultSet.getString("userID")); - next = vereinfachtesResultSet.next(); - } - return result; - } - - Integer getGroupByStudent(StudentIdentifier student) { - Integer result; - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT groupId FROM `groupuser` WHERE `projectName`=? AND `userName`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, student.getProjectId(), student.getStudentId()); - vereinfachtesResultSet.next(); - result = vereinfachtesResultSet.getInt("groupId"); - return result; - } - - ArrayList<String> getStudentsByGroupAndProject(Integer groupId, String projectName) { - ArrayList<String> result = new ArrayList<>(); - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `groupuser` WHERE `groupId`=? AND `projectName`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, groupId, projectName); - Boolean next = vereinfachtesResultSet.next(); - while (next) { - result.add(vereinfachtesResultSet.getString("userName")); - next = vereinfachtesResultSet.next(); - } - return result; - } - - ArrayList<Map<String, Double>> getContributionRating(Integer groupId) { - ArrayList<Map<String, Double>> result = new ArrayList<>(); - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `contributionrating` WHERE `groupId`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, groupId); - boolean next = vereinfachtesResultSet.next(); - while (next) { - Map<String, Double> contributionRating = new HashMap<>(); - for (String category : Categories.contributionRatingCategories) { - contributionRating.put(category, (double) vereinfachtesResultSet.getInt(category)); - } - result.add(contributionRating); - next = vereinfachtesResultSet.next(); - } - connect.close(); - return result; - } - - Boolean getContributionRating(Integer groupId, String fromStudent) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `contributionrating` WHERE `groupId`=? AND `fromPeer`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, groupId, fromStudent); - return vereinfachtesResultSet.next(); - } - - Integer getQuizCount(String projectName){ - Integer result = 0; - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `quiz` WHERE `projectName`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, projectName); - Boolean next = vereinfachtesResultSet.next(); - while (next){ - result++; - next = vereinfachtesResultSet.next(); - } - return result; - } - - ArrayList<Integer> getAnsweredQuizzes(StudentIdentifier student) { - ArrayList<Integer> result = new ArrayList<>(); - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `answeredquiz` WHERE `projectName`=? AND `userName`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, student.getProjectId(), student.getStudentId()); - boolean next = vereinfachtesResultSet.next(); - while (next) { - result.add(vereinfachtesResultSet.getInt("correct")); - next = vereinfachtesResultSet.next(); - } - connect.close(); - return result; - } - - void writeAnsweredQuiz(StudentIdentifier student, Map<String, Boolean> questions) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - for (String question : questions.keySet()) { - String mysqlRequest = "INSERT INTO `answeredquiz`(`projectName`, `userName`, `question`, `correct`) VALUES (?,?,?,?)"; - connect.issueInsertOrDeleteStatement(mysqlRequest, - student.getProjectId(), - student.getStudentId(), - question, - questions.get(question) - ); - } - connect.close(); - } - - void writeWorkRatingToDB(StudentIdentifier student, String fromStudent, Map<String, Integer> workRating) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "INSERT INTO `workrating`(`projectName`, `userName`, `fromPeer`, " + - "`responsibility`, " + - "`partOfWork`, " + - "`cooperation`, " + - "`communication`, " + - "`autonomous`" + - ") VALUES (?,?,?,?,?,?,?,?)"; - connect.issueInsertOrDeleteStatement(mysqlRequest, student.getProjectId(), student.getStudentId(), fromStudent, - workRating.get("responsibility"), - workRating.get("partOfWork"), - workRating.get("cooperation"), - workRating.get("communication"), - workRating.get("autonomous") - ); - connect.close(); - } - - Integer getWhichGroupToRate(StudentIdentifier student) { - Integer result; - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest1 = "SELECT groupId FROM `groupuser` WHERE `projectName`=? AND `userName`=? "; - VereinfachtesResultSet vereinfachtesResultSet1 = - connect.issueSelectStatement(mysqlRequest1, student.getProjectId(), student.getStudentId()); - vereinfachtesResultSet1.next(); - Integer groupId = vereinfachtesResultSet1.getInt("groupId"); - - String mysqlRequest2 = "SELECT DISTINCT groupId FROM `groupuser` WHERE `projectName`=? "; - VereinfachtesResultSet vereinfachtesResultSet2 = - connect.issueSelectStatement(mysqlRequest2, student.getProjectId()); - Boolean next = vereinfachtesResultSet2.next(); - result = vereinfachtesResultSet2.getInt("groupId"); - while (next) { - if (vereinfachtesResultSet2.getInt("groupId") == groupId) { - next = vereinfachtesResultSet2.next(); - if (next) { - result = vereinfachtesResultSet2.getInt("groupId"); - } - } else { - next = vereinfachtesResultSet2.next(); - } - - } - connect.close(); - return result; - } - - void writeContributionRatingToDB(String groupId, String fromStudent, Map<String, Integer> contributionRating) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "INSERT INTO `contributionrating`(" + - "`groupId`, " + - "`fromPeer`, " + - "`dossier`, " + - "`research`) " + - "VALUES (?,?,?,?)"; - connect.issueInsertOrDeleteStatement(mysqlRequest, - groupId, - fromStudent, - contributionRating.get("dossier"), - contributionRating.get("research") - ); - connect.close(); - } - - void writeGradesToDB(Map<StudentIdentifier, Double> grade) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "INSERT INTO `grades`(`projectName`, `userName`, `grade`) VALUES (?,?,?)"; - for (StudentIdentifier student: grade.keySet()) { - connect.issueInsertOrDeleteStatement(mysqlRequest, - student.getProjectId(), - student.getStudentId(), - grade.get(student)); - } - connect.close(); - } - - Double getGradesFromDB(StudentIdentifier student) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `grades` WHERE `projectName`=? AND `userName`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, student.getProjectId(), student.getStudentId()); - vereinfachtesResultSet.next(); - return vereinfachtesResultSet.getDouble("grade"); - } - - Map<String, Boolean> getAnswers(String projectName, String question) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - Map<String, Boolean> result = new HashMap<>(); - String mysqlRequest = "SELECT * FROM `quiz` WHERE `projectName`=? AND `question`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, projectName, question); - boolean next = vereinfachtesResultSet.next(); - while (next) { - result.put(vereinfachtesResultSet.getString("answer"), vereinfachtesResultSet.getBoolean("correct")); - next = vereinfachtesResultSet.next(); - } - connect.close(); - return result; - } - - Map<StudentIdentifier, ConstraintsMessages> missingAssessments(String projectName) { - Map<StudentIdentifier, ConstraintsMessages> result = new HashMap<>(); - ArrayList<String> studentsInProject = new ArrayList<>(getStudents(projectName)); - ArrayList<StudentIdentifier> missingStudentsCauseOfWorkrating = missingWorkRatings(studentsInProject, projectName); - if (missingStudentsCauseOfWorkrating != null) - for (StudentIdentifier missingStudent : missingStudentsCauseOfWorkrating) { - result.put(missingStudent, new ConstraintsMessages(Constraints.AssessmentOpen, missingStudent)); - } - // ArrayList<StudentIdentifier> missingStudentsCauseOfQuiz <--- I can't check that atm - ArrayList<StudentIdentifier> missingStudentsCauseOfContribution = missingContribution(studentsInProject, projectName); - if (missingStudentsCauseOfContribution != null) - for (StudentIdentifier missingStudent : missingStudentsCauseOfContribution) { - result.put(missingStudent, new ConstraintsMessages(Constraints.AssessmentOpen, missingStudent)); - } - return result; - } - - private ArrayList<StudentIdentifier> missingWorkRatings(ArrayList<String> studentsInProject, String projectName) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - ArrayList<StudentIdentifier> result = new ArrayList<>(); - String sqlSelectWorkRating = "SELECT DISTINCT fromPeer FROM `workrating` WHERE `projectName`='"+projectName+"' AND `fromPeer`=''"; - for (String userName : studentsInProject) { - sqlSelectWorkRating = sqlSelectWorkRating + " OR `fromPeer`='" + userName+"'"; - } - VereinfachtesResultSet selectWorkRatingResultSet = - connect.issueSelectStatement(sqlSelectWorkRating); - Boolean next = selectWorkRatingResultSet.next(); - resultSetToStudentIdentifierList(studentsInProject, projectName, result, selectWorkRatingResultSet, next); - return result; - } - - private ArrayList<StudentIdentifier> missingContribution(ArrayList<String> studentsInProject, String projectName) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - ArrayList<StudentIdentifier> result = new ArrayList<>(); - String sqlContribution = "SELECT DISTINCT cr.fromPeer FROM groupuser gu " + - "JOIN contributionrating cr ON gu.groupId=cr.groupId WHERE gu.projectName = ?;"; - VereinfachtesResultSet selectContributionResultSet = - connect.issueSelectStatement(sqlContribution, projectName); - Boolean next = selectContributionResultSet.next(); - resultSetToStudentIdentifierList(studentsInProject, projectName, result, selectContributionResultSet, next); - return result; - } - - private void resultSetToStudentIdentifierList(ArrayList<String> studentsInProject, String projectName, ArrayList<StudentIdentifier> result, VereinfachtesResultSet selectWorkRatingResultSet, Boolean next) { - while (next) { - String fromPeer = selectWorkRatingResultSet.getString("fromPeer"); - if (!studentsInProject.contains(fromPeer)) { - StudentIdentifier userNameentifier = new StudentIdentifier(projectName, fromPeer); - result.add(userNameentifier); - } - next = selectWorkRatingResultSet.next(); - } - } - -} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/controller/service/QuizDBCommunication.java.orig b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/controller/service/QuizDBCommunication.java.orig deleted file mode 100644 index c41fde8a2c08d2c85c03f7317d591e64d82c83ad..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/controller/service/QuizDBCommunication.java.orig +++ /dev/null @@ -1,139 +0,0 @@ -package unipotsdam.gf.modules.assessment.controller.service; - -import unipotsdam.gf.core.database.mysql.MysqlConnect; -import unipotsdam.gf.core.database.mysql.VereinfachtesResultSet; -import unipotsdam.gf.modules.assessment.controller.model.Quiz; - -import javax.annotation.ManagedBean; -import javax.annotation.Resource; -import javax.inject.Singleton; -import java.util.ArrayList; - -@ManagedBean -@Resource -@Singleton -public class QuizDBCommunication { - Quiz getQuizByProjectQuizId(String projectName, String quizId, String authorEmail) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "SELECT * FROM `quiz` WHERE `projectName`=? AND `question`=? AND `authorEmail`=?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, projectName, quizId, authorEmail); - boolean next = vereinfachtesResultSet.next(); - String question = ""; - ArrayList<String> correctAnswers = new ArrayList<>(); - ArrayList<String> incorrectAnswers = new ArrayList<>(); - String answer; - Boolean correct; - String mcType = ""; - while (next) { - mcType = vereinfachtesResultSet.getString("mcType"); - question = vereinfachtesResultSet.getString("question"); - answer = vereinfachtesResultSet.getString("answer"); - correct = vereinfachtesResultSet.getBoolean("correct"); - if (correct) { - correctAnswers.add(answer); - } else { - incorrectAnswers.add(answer); - } - next = vereinfachtesResultSet.next(); - } - Quiz quiz = new Quiz(mcType, question, correctAnswers, incorrectAnswers); - connect.close(); - return quiz; - } - - ArrayList<Quiz> getQuizByProjectId(String projectName) { - String mysqlRequest = "SELECT * FROM quiz where projectName= ?"; - return RequestToQuizList(mysqlRequest, projectName); - } - - - ArrayList<Quiz> getQuizByProjectIdAuthor(String projectName, String authorEmail){ - String mysqlRequest = "SELECT * FROM quiz where projectName= ? AND authorEmail=?"; - return RequestToQuizList(mysqlRequest, projectName, authorEmail); - } - - private ArrayList<Quiz> RequestToQuizList(String sqlRequest, Object ... params) { - MysqlConnect connect = new MysqlConnect(); -<<<<<<< HEAD -======= - ArrayList<Quiz> result = new ArrayList<>(); ->>>>>>> origin/development_master - connect.connect(); - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(sqlRequest, params); - boolean next = vereinfachtesResultSet.next(); - ArrayList<Quiz> result = new ArrayList<>(); - String question; - ArrayList<String> correctAnswers = new ArrayList<>(); - ArrayList<String> incorrectAnswers = new ArrayList<>(); - String answer; - String oldQuestion = ""; - Boolean correct; - String mcType = ""; - Quiz quiz; - while (next) { - mcType = vereinfachtesResultSet.getString("mcType"); - question = vereinfachtesResultSet.getString("question"); - answer = vereinfachtesResultSet.getString("answer"); - correct = vereinfachtesResultSet.getBoolean("correct"); - if (oldQuestion.equals(question)) { - if (correct) { - correctAnswers.add(answer); - } else { - incorrectAnswers.add(answer); - } - } else { - quiz = new Quiz(mcType, oldQuestion, correctAnswers, incorrectAnswers); - result.add(quiz); - correctAnswers = new ArrayList<>(); - incorrectAnswers = new ArrayList<>(); - if (correct) { - correctAnswers.add(answer); - } else { - incorrectAnswers.add(answer); - } - - } - oldQuestion = question; - next = vereinfachtesResultSet.next(); - } - quiz = new Quiz(mcType, oldQuestion, correctAnswers, incorrectAnswers); - result.add(quiz); - return result; - } - - public void deleteQuiz(String quizId) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mysqlRequest = "DELETE FROM quiz where question = (?)"; - connect.issueInsertOrDeleteStatement(mysqlRequest, quizId); - connect.close(); - } - - public void createQuiz(Quiz quiz, String authorEmail, String projectName) { - MysqlConnect connect = new MysqlConnect(); - connect.connect(); - String mcType; - String question; - String answer; - ArrayList<String> correctAnswers = quiz.getCorrectAnswers(); - for (String correctAnswer : correctAnswers) { - answer = correctAnswer; - mcType = quiz.getType(); - question = quiz.getQuestion(); - String mysqlRequest = "INSERT INTO `quiz`(`authorEmail`, `projectName`, `question`, `mcType`, `answer`, `correct`) VALUES (?,?,?,?,?,?)"; - connect.issueInsertOrDeleteStatement(mysqlRequest, authorEmail, projectName, question, mcType, answer, true); - } - ArrayList<String> incorrectAnswers = quiz.getIncorrectAnswers(); - for (String incorrectAnswer : incorrectAnswers) { - answer = incorrectAnswer; - mcType = quiz.getType(); - question = quiz.getQuestion(); - String mysqlRequest = "INSERT INTO `quiz`(`authorEmail`, `projectName`, `question`, `mcType`, `answer`, `correct`) VALUES (?,?,?,?,?,?)"; - connect.issueInsertOrDeleteStatement(mysqlRequest, authorEmail, projectName, question, mcType, answer, false); - } - connect.close(); - } -} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/controller/view/QuizView.java.orig b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/controller/view/QuizView.java.orig deleted file mode 100644 index 9121f5106de696040ca3ae406f29f864e295f4f2..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/assessment/controller/view/QuizView.java.orig +++ /dev/null @@ -1,239 +0,0 @@ -package unipotsdam.gf.modules.assessment.controller.view; - -<<<<<<< HEAD -import unipotsdam.gf.core.management.ManagementImpl; -import Project; -import User; -import unipotsdam.gf.interfaces.IPeerAssessment; -import unipotsdam.gf.modules.assessment.controller.model.*; -import unipotsdam.gf.modules.assessment.controller.service.PeerAssessment; - -import javax.ws.rs.*; -======= -import unipotsdam.gf.interfaces.IPeerAssessment; -import unipotsdam.gf.modules.assessment.controller.model.Assessment; -import unipotsdam.gf.modules.assessment.controller.model.PeerRating; -import unipotsdam.gf.modules.assessment.controller.model.Performance; -import unipotsdam.gf.modules.assessment.controller.model.Quiz; -import unipotsdam.gf.modules.assessment.controller.model.StudentAndQuiz; -import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; -import unipotsdam.gf.modules.assessment.controller.service.PeerAssessment; - -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; ->>>>>>> origin/development_master -import javax.ws.rs.core.MediaType; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Path("/assessments") -public class QuizView { - private static IPeerAssessment peer = new PeerAssessment(); //correct DB-conn and stuff - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/project/{projectName}/quiz/{quizId}/authorEmail/{authorEmail}") - public Quiz getQuiz(@PathParam("projectName") String projectName, @PathParam("quizId") String quizId, @PathParam("authorEmail") String authorEmail) { - try { - String question = java.net.URLDecoder.decode(quizId, "UTF-8"); - return peer.getQuiz(projectName, question, authorEmail); - } catch (UnsupportedEncodingException e) { - throw new AssertionError("UTF-8 is unknown"); - } - } ///////////////////////////////funktioniert////////////////////////////////// - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/project/{projectName}/quiz/authorEmail/{authorEmail}") - public ArrayList<Quiz> getQuiz(@PathParam("projectName") String projectName, @PathParam("authorEmail") String authorEmail) { - return peer.getQuiz(projectName, authorEmail); - } - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/project/{projectName}/quiz") - public ArrayList<Quiz> getQuiz(@PathParam("projectName") String projectName) { - return peer.getQuiz(projectName); - } - //////////////////////////////////////////funktioniert/////////////////////////////////////// - - - @POST - @Consumes(MediaType.APPLICATION_JSON) - @Path("/peerRating/project/{projectName}") - public void postPeerRating(ArrayList<PeerRating> peerRatings, @PathParam("projectName") String projectName) throws IOException { - peer.postPeerRating(peerRatings, projectName); - } - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/groupRate/project/{projectName}/student/{userName}") - public Integer whichGroupToRate(@PathParam("projectName") String projectName, @PathParam("userName") String userName) { - StudentIdentifier student = new StudentIdentifier(projectName, userName); - return peer.whichGroupToRate(student); - } - - @POST - @Consumes(MediaType.APPLICATION_JSON) - @Path("/contributionRating/group/{groupId}/fromPeer/{fromPeer}") - public void postContributionRating(Map<String, Integer> contributionRatings, - @PathParam("groupId") String groupId, - @PathParam("fromPeer") String fromPeer) throws IOException { - peer.postContributionRating(groupId, fromPeer, contributionRatings); - } - - @POST - @Consumes(MediaType.APPLICATION_JSON) - @Path("/quizAnswer/projectName/{projectName}/userName/{userName}/") - public void answerQuiz(Map<String, List<String>> questions, @PathParam("projectName") String projectName, @PathParam("userName") String userName) { - StudentIdentifier student = new StudentIdentifier(projectName, userName); - peer.answerQuiz(questions, student); - } - - @POST - @Consumes(MediaType.APPLICATION_JSON) - @Path("/quiz/{quizId}") - public void deleteQuiz(@PathParam("quizId") String quizId) { - try { - String question = java.net.URLDecoder.decode(quizId, "UTF-8"); - peer.deleteQuiz(question); - } catch (UnsupportedEncodingException e) { - throw new AssertionError("UTF-8 is unknown"); - } - } - - @GET - @Produces(MediaType.TEXT_HTML) - @Path("/whatToRate/project/{projectName}/student/{userName}") - public String whatToRate(@PathParam("projectName") String projectName, @PathParam("userName") String userName) { - StudentIdentifier student = new StudentIdentifier(projectName, userName); - return peer.whatToRate(student); - } - - /*@POST - @Produces(MediaType.TEXT_PLAIN) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/assessment") - public void addAssessmentDataToDB(Assessment assessment) { - peer.addAssessmentDataToDB(assessment); - }*/ - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/get/project/{projectName}") - public Map<StudentIdentifier, Double> getAssessmentForProject(String projectName) { - return peer.getAssessmentForProject(projectName); - } - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/get/project/{projectName}/student/{userName}") - public Double getAssessmentForStudent(@PathParam("projectName") String projectName, @PathParam("userName") String userName) { - StudentIdentifier student = new StudentIdentifier(projectName, userName); - return peer.getAssessmentForStudent(student); - } - - - @POST - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.TEXT_HTML) - @Path("/quiz") - public String createQuiz(StudentAndQuiz studentAndQuiz) { - ManagementImpl management = new ManagementImpl(); - Project project = management.getProjectById(studentAndQuiz.getStudentIdentifier().getProjectId()); - User user = management.getUserByName(studentAndQuiz.getStudentIdentifier().getStudentId()); - Boolean isStudent = user.getStudent(); - peer.createQuiz(studentAndQuiz); - if (isStudent) { - return "student"; - } else { - return "docent"; - } - } - ////////////////////////////////funktioniert/////////////////////////////////////////// -//todo: is unnecessary I guess. finalizing should just happen when phase ends - @POST - @Consumes(MediaType.APPLICATION_JSON) - @Path("/finalize/project/{projectName}") - public String calculateAssessment(@PathParam("projectName") String projectName) { - peer.finalizeAssessment(projectName); - return "successfully finalized "+projectName; - } - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/mean/project/{projectName}") - public int meanOfAssessment(@PathParam("projectName") String ProjectId) { - return peer.meanOfAssessment(ProjectId); - } ///////////////////////////////return 0////////////////////////////////// - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/total/project/{projectName}/student/{student}") - public ArrayList<Performance> getTotalAssessment(@PathParam("projectName") String ProjectId, @PathParam("student") String student) { - StudentIdentifier userNameentifier = new StudentIdentifier(ProjectId, student); - return getTotalAssessment(userNameentifier); - } //////////dummy/////////////funktioniert wie geplant////////////////////////////////// - - private ArrayList<Performance> getTotalAssessment(StudentIdentifier userNameentifier) { - return peer.getTotalAssessment(userNameentifier); - } /////////dummy/////////////funktioniert wie geplant////////////////////////////////// - - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/dummy/totalperformance") - public List<Performance> getTotalAssessment() { - List<Performance> result = new ArrayList<>(); - StudentIdentifier student = new StudentIdentifier("projekt", "student"); - List<Integer> quiz = new ArrayList<>(); - quiz.add(1); - quiz.add(0); - quiz.add(1); - quiz.add(0); - quiz.add(1); - quiz.add(0); - quiz.add(1); - Map<String, Double> work = new HashMap<>(); - work.put("responsibility", 1.); - work.put("partOfWork", 1.); - work.put("cooperation", 1.); - work.put("communication", 1.); - work.put("autonomous", 1.); - Map<String, Double> work2 = new HashMap<>(); - work2.put("responsibility", 3.); - work2.put("partOfWork", 4.); - work2.put("cooperation", 5.); - work2.put("communication", 3.); - work2.put("autonomous", 4.); - Map<String, Double> contribution1 = new HashMap<>(); - contribution1.put("Dossier", 4.); - contribution1.put("eJournal", 2.); - contribution1.put("research", 4.); - Map<String, Double> contribution2 = new HashMap<>(); - contribution2.put("Dossier", 2.); - contribution2.put("eJournal", 3.); - contribution2.put("research", 4.); - Performance pf = new Performance(); - pf.setContributionRating(contribution1); - pf.setQuizAnswer(quiz); - pf.setStudentIdentifier(student); - pf.setWorkRating(work); - Performance pf2 = new Performance(); - pf2.setContributionRating(contribution2); - pf2.setQuizAnswer(quiz); - pf2.setStudentIdentifier(student); - pf2.setWorkRating(work2); - result.add(pf); - result.add(pf2); - return result; - } /////////dummy////////////returns what i expect it to return!!!!!////////////////////////////////// -} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/model/LoginToken.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/model/LoginToken.java new file mode 100644 index 0000000000000000000000000000000000000000..67b37b22728e42f4cce4fdb67a3c412e1f2f6933 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/model/LoginToken.java @@ -0,0 +1,21 @@ +package unipotsdam.gf.modules.communication.model; + +public class LoginToken { + private String loginToken; + + + public LoginToken(String loginToken) { + this.loginToken = loginToken; + } + + public LoginToken( ) { + } + + public String getLoginToken() { + return loginToken; + } + + public void setLoginToken(String loginToken) { + this.loginToken = loginToken; + } +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/model/RocketChatUser.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/model/RocketChatUser.java new file mode 100644 index 0000000000000000000000000000000000000000..131f285cc8ceb00e2338eaca9207499f78cf1991 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/model/RocketChatUser.java @@ -0,0 +1,50 @@ +package unipotsdam.gf.modules.communication.model; + +import unipotsdam.gf.modules.user.User; + +public class RocketChatUser extends User { + private String rocketChatAuthToken; + // not in this version + private String rocketChatPersonalAccessToken; + // the actual id that oculd be used instead of the email with the queries + private String rocketChatUserId; + + public RocketChatUser(String name, String password, String email, String rocketChatUsername, + String rocketChatAuthToken, String rocketChatPersonalAccessToken, String rocketChatUserId, + Boolean isStudent) { + super(name, password,email,rocketChatUsername, isStudent); + this.rocketChatAuthToken = rocketChatAuthToken; + this.rocketChatPersonalAccessToken = rocketChatPersonalAccessToken; + this.rocketChatUserId = rocketChatUserId; + + } + + public RocketChatUser() { + + } + + + public String getRocketChatUserId() { + return rocketChatUserId; + } + + public void setRocketChatUserId(String rocketChatUserId) { + this.rocketChatUserId = rocketChatUserId; + } + + public String getRocketChatAuthToken() { + return rocketChatAuthToken; + } + + public void setRocketChatAuthToken(String rocketChatAuthToken) { + this.rocketChatAuthToken = rocketChatAuthToken; + } + + public String getRocketChatPersonalAccessToken() { + return rocketChatPersonalAccessToken; + } + + public void setRocketChatPersonalAccessToken(String rocketChatPersonalAccessToken) { + this.rocketChatPersonalAccessToken = rocketChatPersonalAccessToken; + } +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/model/rocketChat/RocketChatSuccessResponse.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/model/rocketChat/RocketChatSuccessResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..8fa718f0eb8cbc88b8d18141618dd217a43e763c --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/model/rocketChat/RocketChatSuccessResponse.java @@ -0,0 +1,19 @@ +package unipotsdam.gf.modules.communication.model.rocketChat; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class RocketChatSuccessResponse { + private String success; + + public RocketChatSuccessResponse() { + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/service/CommunicationService.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/service/CommunicationService.java index a82bdd7b482a2ea6a1ac0182398aa28995f99f15..7e2a4734664bae88631544cd9234037e31d997c0 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/service/CommunicationService.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/service/CommunicationService.java @@ -7,12 +7,17 @@ import org.apache.logging.log4j.util.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import unipotsdam.gf.config.GFRocketChatConfig; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; +import unipotsdam.gf.exceptions.UserExistsInRocketChatException; import unipotsdam.gf.interfaces.ICommunication; import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; import unipotsdam.gf.modules.communication.model.EMailMessage; +import unipotsdam.gf.modules.communication.model.RocketChatUser; import unipotsdam.gf.modules.communication.model.chat.ChatMessage; import unipotsdam.gf.modules.communication.model.rocketChat.RocketChatLoginResponse; import unipotsdam.gf.modules.communication.model.rocketChat.RocketChatRegisterResponse; +import unipotsdam.gf.modules.communication.model.rocketChat.RocketChatSuccessResponse; import unipotsdam.gf.modules.communication.util.RocketChatHeaderMapBuilder; import unipotsdam.gf.modules.group.Group; import unipotsdam.gf.modules.group.GroupDAO; @@ -69,7 +74,7 @@ public class CommunicationService implements ICommunication { } - private static Boolean isUpdated; +/* private static Boolean isUpdated; private static synchronized Boolean setAdminToken() { if (isUpdated == null) { @@ -87,7 +92,7 @@ public class CommunicationService implements ICommunication { isUpdated = false; } return null; - } + }*/ @Override public List<ChatMessage> getChatHistory(String roomId) { @@ -102,15 +107,17 @@ public class CommunicationService implements ICommunication { } @Override - public String createEmptyChatRoom(String name, boolean readOnly) { + public String createEmptyChatRoom(String name, boolean readOnly) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { return createChatRoom(name, readOnly, new ArrayList<>()); } @Override - public String createChatRoom(String name, boolean readOnly, List<User> member) { - loginUser(ADMIN_USER); + public String createChatRoom(String name, boolean readOnly, List<User> member) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { + //loginUser(ADMIN_USER); - Map<String, String> headerMap = new RocketChatHeaderMapBuilder().withRocketChatAdminAuth().build(); + Map<String, String> headerMap = new RocketChatHeaderMapBuilder().withRocketChatAdminAuth(this).build(); List<String> usernameList = member.stream().map(User::getRocketChatUsername).collect(Collectors.toList()); HashMap<String, Object> bodyMap = new HashMap<>(); @@ -141,11 +148,12 @@ public class CommunicationService implements ICommunication { } @Override - public boolean createChatRoom(Group group, boolean readOnly) { - loginUser(ADMIN_USER); + public boolean createChatRoom(Group group, boolean readOnly) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { + //loginUser(ADMIN_USER); // chatRoom name: projectId - GroupId - String chatRoomName = String.join("-", group.getProjectName(), String.valueOf(group.getId())); + String chatRoomName = getChatRoomName(group); if (exists(chatRoomName)) { return true; } @@ -159,11 +167,25 @@ public class CommunicationService implements ICommunication { } @Override - public boolean deleteChatRoom(String roomId) { + public void deleteChatRoom(Group group) throws RocketChatDownException, UserDoesNotExistInRocketChatException { + deleteChatRoom(getChatRoomName(group)); + } + + @Override + public void deleteChatRoom(Project project) throws RocketChatDownException, UserDoesNotExistInRocketChatException { + deleteChatRoom(project.getName()); + } + + private String getChatRoomName(Group group) { + return String.join("-", group.getProjectName(), String.valueOf(group.getId())); + } + + @Override + public boolean deleteChatRoom(String roomId) throws RocketChatDownException, UserDoesNotExistInRocketChatException { // TODO: maybe add lock for getChatRoomName, so synchronized access doesn't create errors while deleting - loginUser(ADMIN_USER); + //loginUser(ADMIN_USER); - Map<String, String> headerMap = new RocketChatHeaderMapBuilder().withRocketChatAdminAuth().build(); + Map<String, String> headerMap = new RocketChatHeaderMapBuilder().withRocketChatAdminAuth(this).build(); HashMap<String, String> bodyMap = new HashMap<>(); bodyMap.put("roomId", roomId); @@ -184,25 +206,29 @@ public class CommunicationService implements ICommunication { } @Override - public boolean addUserToChatRoom(User user, String roomId) { + public boolean addUserToChatRoom(User user, String roomId) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { return modifyChatRoom(user, roomId, true); } @Override - public boolean removeUserFromChatRoom(User user, String roomId) { + public boolean removeUserFromChatRoom(User user, String roomId) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { return modifyChatRoom(user, roomId, false); } - private boolean modifyChatRoom(User user, String roomId, boolean addUser) { - loginUser(ADMIN_USER); + private boolean modifyChatRoom(User user, String roomId, boolean addUser) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { + //loginUser(ADMIN_USER); + RocketChatUser student = loginUser(user); - if (hasEmptyParameter(user.getRocketChatUserId(), user.getRocketChatPersonalAccessToken(), roomId)) { + if (hasEmptyParameter(user.getRocketChatUsername(), roomId)) { return false; } - Map<String, String> headerMap = new RocketChatHeaderMapBuilder().withRocketChatAdminAuth().build(); + Map<String, String> headerMap = new RocketChatHeaderMapBuilder().withRocketChatAdminAuth(this).build(); Map<String, String> bodyMap = new HashMap<>(); bodyMap.put("roomId", roomId); - bodyMap.put("userId", user.getRocketChatUserId()); + bodyMap.put("userId", student.getRocketChatUserId()); String groupUrl = addUser ? "groups.invite" : "groups.kick"; @@ -225,11 +251,11 @@ public class CommunicationService implements ICommunication { } @Override - public String getChatRoomName(String roomId) { + public String getChatRoomName(String roomId) throws RocketChatDownException, UserDoesNotExistInRocketChatException { - loginUser(ADMIN_USER); + //loginUser(ADMIN_USER); - Map<String, String> headerMap = new RocketChatHeaderMapBuilder().withRocketChatAdminAuth().build(); + Map<String, String> headerMap = new RocketChatHeaderMapBuilder().withRocketChatAdminAuth(this).build(); HttpResponse<Map> response = unirestService.get(ROCKET_CHAT_API_LINK + "groups.info").headers(headerMap) .queryString("roomId", roomId).asObject(Map.class); @@ -248,12 +274,13 @@ public class CommunicationService implements ICommunication { } @Override - public boolean loginUser(User user) { + public RocketChatUser loginUser(User user) throws RocketChatDownException, UserDoesNotExistInRocketChatException { if (hasEmptyParameter(user.getEmail(), user.getPassword())) { - return false; + return null; } + // HashMap<String, String> rocketChatAuth = new HashMap<>(); rocketChatAuth.put("user", user.getEmail()); rocketChatAuth.put("password", user.getPassword()); @@ -263,30 +290,37 @@ public class CommunicationService implements ICommunication { .asObject(RocketChatLoginResponse.class); if (isBadRequest(response)) { - return false; + throw new UserDoesNotExistInRocketChatException(); } else { - if (ADMIN_USER.equals(user)) { + /*if (ADMIN_USER.equals(user)) { setAdminToken(); - } + }*/ } RocketChatLoginResponse rocketChatLoginResponse = response.getBody(); - user.setRocketChatUserId(rocketChatLoginResponse.getUserId()); - user.setRocketChatAuthToken(rocketChatLoginResponse.getAuthToken()); - return true; + RocketChatUser rocketChatUser = new RocketChatUser(); + rocketChatUser.setEmail(user.getEmail()); + rocketChatUser.setPassword(user.getPassword()); + rocketChatUser.setRocketChatUserId(rocketChatLoginResponse.getUserId()); + rocketChatUser.setRocketChatAuthToken(rocketChatLoginResponse.getAuthToken()); + + return rocketChatUser; } @Override - public boolean registerUser(User user) { + public boolean registerUser(User user) throws RocketChatDownException, UserExistsInRocketChatException { if (hasEmptyParameter(user.getEmail(), user.getName(), user.getPassword())) { return false; } HashMap<String, String> rocketChatRegister = new HashMap<>(); - String rocketChatUsername = createRocketChatUsername(user); - rocketChatRegister.put("username", rocketChatUsername); + if (user.getRocketChatUsername() == null) { + String rocketChatUsername = createRocketChatUsername(user); + user.setRocketChatUsername(rocketChatUsername); + } + rocketChatRegister.put("username", user.getRocketChatUsername()); rocketChatRegister.put("email", user.getEmail()); rocketChatRegister.put("pass", user.getPassword()); rocketChatRegister.put("name", user.getName()); @@ -295,19 +329,20 @@ public class CommunicationService implements ICommunication { unirestService.post(ROCKET_CHAT_API_LINK + "users.register").body(rocketChatRegister) .asObject(RocketChatRegisterResponse.class); - if (isBadRequest(response)) { - return false; + Boolean badRequest = isBadRequest(response); + if (badRequest) { + throw new UserExistsInRocketChatException(); } RocketChatRegisterResponse registerResponse = response.getBody(); + + // not sure we need this test if (!registerResponse.isSuccessful()) { return false; } - user.setRocketChatUsername(rocketChatUsername); - user.setRocketChatUserId(registerResponse.getUserId()); - - + // updateRocketChatUserName user with rocket chat data + userDAO.updateRocketChatUserName(user); /** * TODO with higher rocket chat version a personal access tokens exist and this function can be used */ @@ -315,9 +350,18 @@ public class CommunicationService implements ICommunication { return true; } - public String getChatRoomLink(String userEmail, String projectName) { + /** + * + * @param userEmail + * @param projectName + * @return + * @throws RocketChatDownException + * @throws UserDoesNotExistInRocketChatException + */ + public String getChatRoomLink(String userEmail, String projectName) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { - loginUser(ADMIN_USER); + //loginUser(ADMIN_USER); String chatRoomId = groupDAO.getGroupChatRoomId(new User(userEmail), new Project(projectName)); if (chatRoomId.isEmpty()) { @@ -332,6 +376,12 @@ public class CommunicationService implements ICommunication { return ROCKET_CHAT_ROOM_LINK + chatRoomName + "?layout=embedded"; } + @Override + public String getProjectChatRoomLink(String projectName) { + return ROCKET_CHAT_ROOM_LINK + projectName + "?layout=embedded"; + } + + // TODO: Think about splitting email and chat communication into different @Override public boolean sendSingleMessage(EMailMessage eMailMessage, User user) { @@ -439,18 +489,57 @@ public class CommunicationService implements ICommunication { user.setRocketChatPersonalAccessToken(responseBody.get("token").toString()); return true; }*/ - private boolean isBadRequest(HttpResponse response) { + private Boolean isBadRequest(HttpResponse response) throws RocketChatDownException { int status = response.getStatus(); + if (Response.Status.OK.getStatusCode() == status) { + return false; + } if (Response.Status.UNAUTHORIZED.getStatusCode() == status) { - unsetAdminToken(); + //unsetAdminToken(); + return true; + } + if (Response.Status.NOT_FOUND.getStatusCode() == status) { + //unsetAdminToken(); + throw new RocketChatDownException(); + } else { + return true; } - return status == Response.Status.BAD_REQUEST.getStatusCode() || status == Response.Status.UNAUTHORIZED - .getStatusCode(); } @Override - public boolean exists(String roomId) { + public boolean exists(String roomId) throws RocketChatDownException, UserDoesNotExistInRocketChatException { return !getChatRoomName(roomId).isEmpty(); } + + public void delete(User user) throws RocketChatDownException, UserDoesNotExistInRocketChatException { + //loginUser(ADMIN_USER); + // we need the rocketchatid + if (!(user instanceof RocketChatUser)) { + // we need the password to delete the user + if (user.getPassword() == null) { + user = userDAO.getUserByEmail(user.getEmail()); + } + // fetchign the rocketchat id + try { + RocketChatUser rocketLeagueUser = loginUser(user); + // the actual delete + Map<String, String> headerMap = new RocketChatHeaderMapBuilder().withRocketChatAdminAuth(this).build(); + Map<String, String> bodyMap = new HashMap<>(); + bodyMap.put("userId", rocketLeagueUser.getRocketChatUserId()); + + HttpResponse<RocketChatSuccessResponse> response = + unirestService.post(ROCKET_CHAT_API_LINK + "users.delete").headers(headerMap).body(bodyMap).asObject(RocketChatSuccessResponse.class); + + Boolean badRequest = isBadRequest(response); + if (badRequest) { + //throw new UserDoesNotExistInRocketChatException(); + } + } catch (UserDoesNotExistInRocketChatException e) { + // wenn der Nutzer nicht existiert, brauchen wir ihn auch nicht löschen + } + } + + + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/util/RocketChatHeaderMapBuilder.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/util/RocketChatHeaderMapBuilder.java index 449c533b39bcddf45737e8a164d247fc79377eab..8def9d716f836665c67762a09823c73d137b9044 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/util/RocketChatHeaderMapBuilder.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/util/RocketChatHeaderMapBuilder.java @@ -1,5 +1,10 @@ package unipotsdam.gf.modules.communication.util; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; +import unipotsdam.gf.modules.communication.model.RocketChatUser; +import unipotsdam.gf.modules.communication.service.CommunicationService; + import java.util.HashMap; import java.util.Map; @@ -23,10 +28,14 @@ public class RocketChatHeaderMapBuilder { return this; } - public RocketChatHeaderMapBuilder withRocketChatAdminAuth() { + public RocketChatHeaderMapBuilder withRocketChatAdminAuth(CommunicationService communicationService) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { // with new version of rocketChat: RocketChatHeaderMapBuilder rocketChatHeaderMapBuilder = withAuthTokenHeader(ADMIN_USER.getRocketChatPersonalAccessToken()); - RocketChatHeaderMapBuilder rocketChatHeaderMapBuilder = withAuthTokenHeader(ADMIN_USER.getRocketChatAuthToken()); - return rocketChatHeaderMapBuilder.withRocketChatUserId(ADMIN_USER.getRocketChatUserId()); + + RocketChatUser admin = communicationService.loginUser(ADMIN_USER); + + RocketChatHeaderMapBuilder rocketChatHeaderMapBuilder = withAuthTokenHeader(admin.getRocketChatAuthToken()); + return rocketChatHeaderMapBuilder.withRocketChatUserId(admin.getRocketChatUserId()); } public Map<String, String> build() { diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/view/CommunicationView.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/view/CommunicationView.java index d1178369f086dab8ef56177949db28aad8dc9c62..d1e34cb5a31875c9770d44247aa88bcaeadee665 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/view/CommunicationView.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/communication/view/CommunicationView.java @@ -2,11 +2,18 @@ package unipotsdam.gf.modules.communication.view; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import unipotsdam.gf.config.GFRocketChatConfig; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; +import unipotsdam.gf.modules.communication.model.LoginToken; +import unipotsdam.gf.modules.communication.model.RocketChatUser; import unipotsdam.gf.modules.user.User; import unipotsdam.gf.interfaces.ICommunication; +import unipotsdam.gf.session.GFContexts; import javax.annotation.ManagedBean; import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -14,6 +21,7 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.List; @@ -32,7 +40,8 @@ public class CommunicationView { @GET @Produces(MediaType.APPLICATION_JSON) @Path("/info/{roomId}") - public Response getChatRoomInformation(@PathParam("roomId") String roomId) { + public Response getChatRoomInformation(@PathParam("roomId") String roomId) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { String chatRoomName = communicationService.getChatRoomName(roomId); if (chatRoomName.isEmpty()) { log.error("chatRoom not found for roomId: {}", roomId); @@ -46,7 +55,8 @@ public class CommunicationView { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("/addUser/{roomId}") - public Response addUserToChatRoom(@PathParam("roomId") String roomId, User user) { + public Response addUserToChatRoom(@PathParam("roomId") String roomId, User user) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { if (isNull(user)) { log.trace("addUser user object was null"); return Response.status(Response.Status.BAD_REQUEST).entity("must provide user").build(); @@ -62,7 +72,9 @@ public class CommunicationView { response = Response.ok(wasAdded).build(); } else { log.error("error while adding user to chat room"); - response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("error while adding user to chatRoom").build(); + response = + Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("error while adding user to chatRoom") + .build(); } return response; } @@ -72,7 +84,8 @@ public class CommunicationView { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("/removeUser/{roomId}") - public Response removeUserFromChatRoom(User user, @PathParam("roomId") String roomId) { + public Response removeUserFromChatRoom(User user, @PathParam("roomId") String roomId) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { if (isNull(user)) { log.trace("removeUser user object was null"); return Response.status(Response.Status.BAD_REQUEST).entity("must provide user").build(); @@ -88,7 +101,9 @@ public class CommunicationView { response = Response.ok(wasRemoved).build(); } else { log.error("error while adding user to chat room"); - response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("error while adding user to chatRoom").build(); + response = + Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("error while adding user to chatRoom") + .build(); } return response; } @@ -97,7 +112,9 @@ public class CommunicationView { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("/room/create/{name}") - public Response createChatRoom(@PathParam("name") String name, List<User> users, @QueryParam("readOnly") boolean readOnly) { + public Response createChatRoom( + @PathParam("name") String name, List<User> users, @QueryParam("readOnly") boolean readOnly) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { if (isNull(name)) { return Response.status(Response.Status.BAD_REQUEST).entity("must provide name as queryParam").build(); } @@ -109,4 +126,31 @@ public class CommunicationView { log.trace("response for createChatRoom: {}", chatId); return Response.status(Response.Status.CREATED).entity(chatId).build(); } + + @POST + @Produces(MediaType.APPLICATION_JSON) + @Path("/sso") + public LoginToken provideLoginToken(@Context HttpServletRequest req, Object payload) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { + if (req.getSession().getAttribute(GFContexts.ROCKETCHATAUTHTOKEN) != null) { + String token = getAuthToken(req); + return new LoginToken(token); + } else { + RocketChatUser user = communicationService.loginUser(GFRocketChatConfig.ADMIN_USER); + return new LoginToken(user.getRocketChatAuthToken()); + } + } + + private String getAuthToken(@Context HttpServletRequest req) { + return req.getSession().getAttribute(GFContexts.ROCKETCHATAUTHTOKEN).toString(); + } + + @GET + @Produces(MediaType.TEXT_HTML) + @Path("/login") + public String provideLoginHTML(@Context HttpServletRequest req) { + String rocketChatIntegration = "<script> window.parent.postMessage({event: 'login-with-token',loginToken:" + + " '"+getAuthToken(req)+"'}, '"+GFRocketChatConfig.ROCKET_CHAT_LINK_0 +"');</script>"; + return rocketChatIntegration; + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/Controller/PeerFeedbackController.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/Controller/PeerFeedbackController.java deleted file mode 100644 index a37b51139bcd46b576221adc4721931e544e2c33..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/Controller/PeerFeedbackController.java +++ /dev/null @@ -1,118 +0,0 @@ -package unipotsdam.gf.modules.feedback.Controller; - -import unipotsdam.gf.mysql.MysqlConnect; -import unipotsdam.gf.mysql.VereinfachtesResultSet; -import unipotsdam.gf.modules.feedback.Category; -import unipotsdam.gf.modules.feedback.Model.Peer2PeerFeedback; - -import javax.inject.Inject; -import java.util.ArrayList; -import java.util.UUID; - -public class PeerFeedbackController { - - @Inject - MysqlConnect connection; - - - - //@Override - public void createPeer2PeerFeedback(Peer2PeerFeedback feedback) { - - // create a new id if we found no id. - String uuid = UUID.randomUUID().toString(); - // while (existsfeedbackId(uuid)) { //Todo: existsfeedbackId - // uuid = UUID.randomUUID().toString(); - // } - - // establish connection - - connection.connect(); - - // build and execute request - String request = "INSERT INTO peerfeedback (`id`, `reciever`, `sender`, `text`, `category`, `filename`) VALUES (?,?,?,?,?,?);"; - connection.issueInsertOrDeleteStatement(request, uuid, feedback.getFeedbackreceiver(), feedback.getFeedbacksender(), feedback.getText(), feedback.getFeedbackcategory(), feedback.getFilename()); - - // close connection - connection.close(); - - // build response annotation - //Annotation annotationResponse = getAnnotation(uuid); - - // return Response.ok().build(); - //return null; - - } - - //@Override - public Peer2PeerFeedback getPeer2PeerFeedback(String id) { - - // establish connection - - connection.connect(); - - // build and execute request - String request = "SELECT * FROM peerfeedback WHERE id = ?;"; - VereinfachtesResultSet rs = connection.issueSelectStatement(request, id); - System.out.print(rs); - - if (rs.next()) { - - // save annotation - Peer2PeerFeedback feedback = getPeerfeedbackFromResultSet(rs); - - // close connection - connection.close(); - System.out.print(feedback); - - return feedback; - } else { - - // close connection - connection.close(); - System.out.print("null"); - return null; - } - - } - - public ArrayList<Peer2PeerFeedback> getAllFeedbacks(String sender) { - - ArrayList<Peer2PeerFeedback> feedbacks = new ArrayList<>(); - - // establish connection - - connection.connect(); - - // build and execute request - String request = "SELECT * FROM peerfeedback WHERE sender= ?;"; - VereinfachtesResultSet rs = connection.issueSelectStatement(request, sender); - - while (rs.next()) { - feedbacks.add(getPeerfeedbackFromResultSet(rs)); - } - - // close connection - connection.close(); - System.out.print(feedbacks); - return feedbacks; - - } - - - private Peer2PeerFeedback getPeerfeedbackFromResultSet(VereinfachtesResultSet rs) { - - String id = rs.getString("id"); - long timestamp = rs.getTimestamp(2).getTime(); - String reciever = rs.getString("reciever"); - String sender = rs.getString("sender"); - String text = rs.getString("text"); - Object category = rs.getObject("category"); - String filename = rs.getString("filename"); - - //AnnotationBody body = new AnnotationBody(title, comment, startCharacter, endCharacter); - - //return new Peer2PeerFeedback("id", 1234, Category.TITEL, "reciever", "sender", "test", "filename"); - return new Peer2PeerFeedback(id, timestamp, Category.TITEL, reciever, sender, text, filename); - } -} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/FeedbackImpl.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/FeedbackImpl.java deleted file mode 100644 index fabd7d37d1b621c40cb0d99b1684bb9c6368d429..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/FeedbackImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -package unipotsdam.gf.modules.feedback; - -import unipotsdam.gf.interfaces.Feedback; -import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; -import unipotsdam.gf.modules.feedback.Model.Peer2PeerFeedback; -import unipotsdam.gf.modules.group.GroupDAO; -import unipotsdam.gf.modules.group.GroupFormationMechanism; -import unipotsdam.gf.modules.project.Project; -import unipotsdam.gf.modules.researchreport.ResearchReport; -import unipotsdam.gf.modules.submission.controller.SubmissionController; -import unipotsdam.gf.modules.user.User; -import unipotsdam.gf.modules.user.UserDAO; -import unipotsdam.gf.process.constraints.ConstraintsMessages; - -import javax.inject.Inject; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class FeedbackImpl implements Feedback { - - - @Inject - private UserDAO userDAO; - - @Inject - private GroupDAO groupDAO; - - @Inject - private SubmissionController submissionController; - - @Override - public void assigningMissingFeedbackTasks(Project project) { - - } - - @Override - public void assignFeedbackTasks(Project project) { - GroupFormationMechanism groupFormationMechanism = groupDAO.getGroupFormationMechanism(project); - - switch (groupFormationMechanism) { - case SingleUser: - List<User> usersByProjectName = userDAO.getUsersByProjectName(project.getName()); - User firstUser = usersByProjectName.get(0); - User lastUser = usersByProjectName.get(usersByProjectName.size()-1); - submissionController.updateFullSubmission(firstUser, lastUser); - for (int i = 0; i <= usersByProjectName.size()-2;i++) { - User feedbackGiver =usersByProjectName.get(i); - User submissionOwner = usersByProjectName.get(i+1); - submissionController.updateFullSubmission(submissionOwner, feedbackGiver); - } - break; - case UserProfilStrategy: - case Manual: - case LearningGoalStrategy: - // TODO implement assigning feedback tasks in case of groups - // consider https://docs.google.com/document/d/1DLuggw7gxLbpbDblDTWVtYC-EI4Tb42y285mdABLC0Q/edit?ts=5bbb1f20# - } - - } - - @Override - public ResearchReport getFeedbackTask(User student) { - return null; - } - - -} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/Model/Peer2PeerFeedback.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/Model/Peer2PeerFeedback.java deleted file mode 100644 index beac0364ae5818ad6f849b2975ddf57d2dc1afa6..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/Model/Peer2PeerFeedback.java +++ /dev/null @@ -1,108 +0,0 @@ -package unipotsdam.gf.modules.feedback.Model; - -import unipotsdam.gf.modules.feedback.Category; - -import static unipotsdam.gf.util.MarkdownUtils.convertMarkdownToHtml; - -/** - * Peer2PeerFeedback Object - * created by Katharina - */ - -public class Peer2PeerFeedback { - - - private String id; - private long timestamp; - private Category category; - private String text; - private String sender; - private String receiver; - private String filename; - - public Peer2PeerFeedback(String id, long timestamp, Category category, String text, String sender, String receiver, String filename) { - this.id = id; - this.timestamp = timestamp; - this.category = category; - this.text = convertMarkdownToHtml(text); - this.sender = sender; - this.receiver = receiver; - this.filename = filename; - } - - public Peer2PeerFeedback() { - - } - - public String getID() { - return id; - } - - public void setID(String id) { - this.id = id; - } - - public long getTimestamp() { - return timestamp; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - - public Category getFeedbackcategory() { - return category; - } - - public void setFeedbackcategory(Category category) { - this.category = category; - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public String getFilename() { - return filename; - } - - public void setFilename(String filename) { - this.filename = filename; - } - - public String getFeedbacksender() { - return sender; - } - - public void setFeedbacksender(String feedbacksender) { - this.sender = sender; - } - - public String getFeedbackreceiver() { - return receiver; - } - - public void setFeedbackreceiver(String feedbackreceiver) { - this.receiver = receiver; - } - - - @Override - public String toString() { - return "Peer2PeerFeedback{" + - "id=" + id + - ", timestamp=" + timestamp + - ", category=" + category + - ", sender='" + sender + - ", receiver=" + receiver + - ", text=" + text + - ", filename=" + filename + - '}'; - } - - -} \ No newline at end of file diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/View/PeerFeedbackView.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/View/PeerFeedbackView.java deleted file mode 100644 index 907730d447679f616bd74cc3e92d6bfd505946a3..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/feedback/View/PeerFeedbackView.java +++ /dev/null @@ -1,60 +0,0 @@ -package unipotsdam.gf.modules.feedback.View; - -import unipotsdam.gf.modules.feedback.Category; -import unipotsdam.gf.modules.feedback.Controller.PeerFeedbackController; -import unipotsdam.gf.modules.feedback.Model.Peer2PeerFeedback; - -import javax.ws.rs.Consumes; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.util.ArrayList; - -@Path("/peerfeedback") -//@Consumes({"application/json"}) -//@Produces({"application/json"}) -//@Consumes(MediaType.APPLICATION_FORM_URLENCODED) -//@Produces(MediaType.TEXT_PLAIN) -@Consumes(MediaType.APPLICATION_FORM_URLENCODED) -@Produces(MediaType.APPLICATION_JSON) -public class PeerFeedbackView { - - //private final Logger log = LoggerFactory.getLogger(PeerFeedbackView.class); - - @POST - @Path("/save") - public Response createPeerfeedback(@FormParam("text") String text, @FormParam("student") String student, @FormParam("id") String id, @FormParam("reciever") String reciever, - @FormParam("sender") String sender, @FormParam("filename") String filename, @FormParam("category") Category category, @FormParam("timestamp") Long timestamp) { - - // save peerfeedback request in database and receive the new peerfeedback object - //PeerFeedbackController controller = new PeerFeedbackController(); - //Peer2PeerFeedback pf = controller.createPeer2PeerFeedback(feedback); - //return Response.ok(pf).build(); - - String message = text + student; - Peer2PeerFeedback feedback = new Peer2PeerFeedback("id", 1234, Category.TITEL, "reciever", "sender", text, "filename"); - PeerFeedbackController controller = new PeerFeedbackController(); - controller.createPeer2PeerFeedback(feedback); - - - return Response.ok().build(); //feedback - - } - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("{sender}") - public Response getPeerfeedback(@PathParam("sender") String sender) { - - PeerFeedbackController controller = new PeerFeedbackController(); - ArrayList<Peer2PeerFeedback> fd = controller.getAllFeedbacks(sender); - return Response.ok(fd).build(); - } - - -} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/EnumeratedProfileQuestion.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/EnumeratedProfileQuestion.java new file mode 100644 index 0000000000000000000000000000000000000000..7c48efbced68608c4f678f5f0904e7249ce19da0 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/EnumeratedProfileQuestion.java @@ -0,0 +1,27 @@ +package unipotsdam.gf.modules.group; + +import java.util.ArrayList; +import java.util.List; + +/** + * If the question asks the user to select an option then + * the question neds to provide a list of options as string + * else: the number of checkboxes is stored and the answer is an index + * note: multiple select is not implemented + */ +public class EnumeratedProfileQuestion extends ProfileQuestion { + private java.util.List<String> options; + + public EnumeratedProfileQuestion() { + super(); + this.options = new ArrayList<>(); + } + + public List<String> getOptions() { + return options; + } + + public void setOptions(List<String> options) { + this.options = options; + } +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/Group.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/Group.java index 0453ce6b9c817164a797f6b4f1834173a4e0f582..f7b551f5fa017eed2ec82e5036251313d9cdb619 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/Group.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/Group.java @@ -11,6 +11,7 @@ public class Group { private List<User> members; private String projectName; private String chatRoomId; + private String name; public Group() { members = new ArrayList<>(); @@ -68,8 +69,6 @@ public class Group { members.add(user); } - - public int getId() { return id; } @@ -88,4 +87,12 @@ public class Group { public int hashCode() { return getId(); } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupDAO.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupDAO.java index ebe009fa95ae7578f60b494a3b9815293027ce64..5d8ed5f03db2b26035d37db1c87026338c6d23f2 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupDAO.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupDAO.java @@ -30,7 +30,7 @@ public class GroupDAO { this.connect = connect; } - ArrayList<String> getStudentsInSameGroupAs(StudentIdentifier student) { + public ArrayList<String> getStudentsInSameGroupAs(StudentIdentifier student) { connect.connect(); ArrayList<String> result = new ArrayList<>(); int groupId= getGroupByStudent(student); @@ -176,10 +176,19 @@ public class GroupDAO { public void clearChatRoomIdOfGroup(String chatRoomId) { connect.connect(); - String mysqlRequest = "update groups SET chatRoomId = ? where chatRoomId = ?"; + String mysqlRequest = "updateRocketChatUserName groups SET chatRoomId = ? where chatRoomId = ?"; connect.issueUpdateStatement(mysqlRequest, "", chatRoomId); connect.close(); } + + public void deleteGroups(Project project) { + String query ="DELETE gu FROM groupuser gu INNER JOIN groups g ON gu.groupId=g.id WHERE g.projectName = ?;"; + String query2="DELETE FROM groups WHERE projectName=?;"; + connect.connect(); + connect.issueInsertOrDeleteStatement(query, project.getName()); + connect.issueInsertOrDeleteStatement(query2, project.getName()); + connect.close(); + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupData.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupData.java new file mode 100644 index 0000000000000000000000000000000000000000..cc78582ee0ab378776f969384f5179a0c2ed9ba4 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupData.java @@ -0,0 +1,23 @@ +package unipotsdam.gf.modules.group; + +import java.util.ArrayList; +import java.util.List; + +public class GroupData { + List<Group> groups; + + public GroupData() { + } + + public GroupData(List<Group> groups) { + this.groups = groups; + } + + public List<Group> getGroups() { + return groups; + } + + public void setGroups(ArrayList<Group> groups) { + this.groups = groups; + } +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupView.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupView.java index ec10d90e4ac6f458e853ecf81507cb3f63d271c5..4c065f1f43f64e3c8be0dca1ce74c15205a8d9e0 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupView.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupView.java @@ -1,5 +1,8 @@ package unipotsdam.gf.modules.group; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; import unipotsdam.gf.modules.project.Management; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.modules.project.ProjectDAO; @@ -14,6 +17,7 @@ import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; @Path("/group") public class GroupView { @@ -25,10 +29,20 @@ public class GroupView { private Management iManagement; @Inject - ProjectDAO projectDAO; + private ProjectDAO projectDAO; @Inject - GroupFormationProcess groupFormationProcess; + private GroupFormationProcess groupFormationProcess; + + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/all/projects/{projectName}") + public GroupData getOrInitializeGroups(@PathParam("projectName") String projectName) { + GroupData data = groupFormationProcess.getOrInitializeGroups(new Project(projectName)); + return data; + } + @GET @Produces(MediaType.APPLICATION_JSON) @@ -81,20 +95,48 @@ public class GroupView { throw new WebApplicationException( "the groupfindingmechanism needs to be one of " + GroupFormationMechanism.values().toString()); } - } + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/get/projects/{projectName}") + public List<Group> getGroups(@PathParam("projectName") String projectName) { + List<Group> result = groupfinding.getGroups(projectDAO.getProjectByName(projectName)); + return result; + } - + /** + * find out if this is used by learning goal + * @param projectName + * @param groups + */ + @Deprecated @POST @Consumes(MediaType.APPLICATION_JSON) @Path("/projects/{projectName}") public void saveGroups(@PathParam("projectName") String projectName, Group[] groups) { Project project = new Project(projectName); - groupfinding.persistGroups(Arrays.asList(groups), project); + groupFormationProcess.saveGroups(Arrays.asList(groups), project); + } + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Path("/projects/{projectName}/groups") + public void persistGroups(@PathParam("projectName") String projectName, GroupData data) { + Project project = new Project(projectName); + groupFormationProcess.saveGroups(data.getGroups(), project); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Path("/projects/{projectName}/groups/finalize") + public void finalizeGroups(@PathParam("projectName") String projectName) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { + Project project = new Project(projectName); + groupFormationProcess.finalize(project); } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupfindingImpl.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupfindingImpl.java index 24374f5e7c960d49199a6e0369b64294ff3adfd1..b0df88bbd61d735ac6abf1038b0bcf29bf1421a9 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupfindingImpl.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/GroupfindingImpl.java @@ -1,11 +1,17 @@ package unipotsdam.gf.modules.group; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; +import unipotsdam.gf.interfaces.ICommunication; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.interfaces.IGroupFinding; import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; +import unipotsdam.gf.modules.user.User; +import unipotsdam.gf.modules.user.UserDAO; import javax.inject.Inject; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class GroupfindingImpl implements IGroupFinding { @@ -13,6 +19,12 @@ public class GroupfindingImpl implements IGroupFinding { @Inject private GroupDAO groupDAO; + @Inject + private UserDAO userDAO; + + @Inject + private ICommunication iCommunication; + @Inject public GroupfindingImpl(GroupDAO groupDAO) { this.groupDAO = groupDAO; @@ -24,6 +36,11 @@ public class GroupfindingImpl implements IGroupFinding { // } + @Override + public GroupFormationMechanism getGFM(Project project){ + return groupDAO.getGroupFormationMechanism(project); + } + @Override public void persistGroups(List<Group> groupComposition, Project project) { for (Group group : groupComposition) { @@ -60,4 +77,76 @@ public class GroupfindingImpl implements IGroupFinding { return participantsNeeded; } + + @Override + public void deleteGroups(Project project){ + groupDAO.deleteGroups(project); + } + + @Override + public List<Group> createRandomGroups(Project project) { + ArrayList<Group> result = new ArrayList<>(); + groupDAO.deleteGroups(project); + List<User> usersByProjectName = userDAO.getUsersByProjectName(project.getName()); + int numberOfUsers = usersByProjectName.size(); + if (usersByProjectName.size()<6){ + Group group = new Group(); + group.getMembers().addAll(usersByProjectName); + result.add(group); + } else { + int numberOf3Groups = getNumberOf3Groups(numberOfUsers); + //int numberOf4Groups = getNumberOf4Groups(numberOfUsers); + + Group group = new Group(); + int i = 1; + group.setName(i + ""); + for (User user : usersByProjectName) { + if (numberOf3Groups > 0) { + numberOf3Groups--; + // TODO insert formula here for the correct groups + if (group.getMembers().size() == 3) { + result.add(group); + group = new Group(); + i++; + group.setName(i + ""); + } + } else { + if (group.getMembers().size() == 4) { + result.add(group); + group = new Group(); + i++; + group.setName(i + ""); + } + } + group.addMember(user); + // set group name 1 more + } + result.add(group); + } + return result; + } + + /** + * after this groups should not be touched by the system + * @param project + */ + @Override + public void finalizeGroups(Project project) throws RocketChatDownException, UserDoesNotExistInRocketChatException { + // create group chat rooms + List<Group> groups = getGroups(project); + for (Group group : groups) { + iCommunication.createChatRoom(group, false); + } + } + + // (number % 3) + (Math.floor(number/3)-(number%3)) = n für alle Zahlen größer als 5 + public int getNumberOf4Groups(Integer number) { + return (number % 3); + } + + + // every number can be divided in factors 4 and 3 as long it is greater then 5 + public int getNumberOf3Groups(Integer number) { + return (number / 3) - (number % 3); + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileDAO.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileDAO.java new file mode 100644 index 0000000000000000000000000000000000000000..8e468e6f61317a5a81787b63a190da7063ca0a62 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileDAO.java @@ -0,0 +1,146 @@ +package unipotsdam.gf.modules.group; + +import unipotsdam.gf.mysql.MysqlConnect; +import unipotsdam.gf.mysql.VereinfachtesResultSet; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class ProfileDAO { + @Inject + private MysqlConnect connect; + + /** + * helper function for persisting questions + * @param profileQuestion + * @return the id of the created question + */ + private int persistHelper(ProfileQuestion profileQuestion) { + + int result = -1; + connect.connect(); + + String query = "INSERT INTO profilequestions (`scaleSize`, `question`) values(?,?)"; + result = connect.issueInsertStatementWithAutoincrement(query, profileQuestion.getScaleSize(), + profileQuestion.getQuestion()); + + connect.close(); + + return result; + } + + /** + * persist questions with enumeration + * @param enumeratedProfileQuestion + */ + public void persist(EnumeratedProfileQuestion enumeratedProfileQuestion) { + int questionId = persistHelper(enumeratedProfileQuestion); + connect.connect(); + String query = "INSERT INTO profilequestionoptions (`profileQuestionId`, `name`) values (?,?)"; + List<String> options = enumeratedProfileQuestion.getOptions(); + for (String option : options) { + connect.issueInsertOrDeleteStatement(query, questionId, option); + } + connect.close(); + } + + /** + * persist questions with scale i.e. 1 to 5 + * @param scaledProfileQuestion + */ + public void persist(ScaledProfileQuestion scaledProfileQuestion) { + persistHelper(scaledProfileQuestion); + } + + /** + * persist an answer + * @param profileQuestionAnswer + */ + public void persist(ProfileQuestionAnswer profileQuestionAnswer) { + connect.connect(); + String query = + "INSERT INTO profilequestionanswer (`profileQuestionId`,`answerIndex`, `selectedAnswer`, " + "`userEmail` values (?,?,?,?)"; + connect.issueInsertOrDeleteStatement(query, profileQuestionAnswer.getQuestion().getId(), + profileQuestionAnswer.getAnswerIndex(), profileQuestionAnswer.getSelectedAnswer(), + profileQuestionAnswer.getUser().getEmail()); + } + + /** + * get all the questions + * @return + */ + public java.util.List<ProfileQuestion> getQuestions() { + ArrayList<ProfileQuestion> profileQuestions = new ArrayList<>(); + connect.connect(); + + String query = + "SELECT (q.id,q.scaleSize, q.question, o.name) from profilequestions q LEFT JOIN " + "profilequestionoptions o where q.id = o" + ".profileQuestionId group by q.id"; + VereinfachtesResultSet vereinfachtesResultSet = connect.issueSelectStatement(query); + HashMap<Integer, ArrayList<String>> optionMap = new HashMap<>(); + List<ProfileQuestion> tmpList = new ArrayList<>(); + while (vereinfachtesResultSet.next()) { + String optionName = vereinfachtesResultSet.getString("name"); + int questionId = vereinfachtesResultSet.getInt("id"); + int scaleSize = vereinfachtesResultSet.getInt("scaleSize"); + String question = vereinfachtesResultSet.getString("question"); + if (optionName != null) { + if (optionMap.containsKey(optionName)) { + ArrayList<String> optionList = optionMap.get(optionName); + optionList.add(optionName); + optionMap.put(questionId, optionList); + } else { + ArrayList<String> optionList = new ArrayList<>(); + optionList.add(optionName); + optionMap.put(questionId, optionList); + } + } + tmpList.add(new ProfileQuestion(questionId, scaleSize, question)); + } + + for (ProfileQuestion question : tmpList) { + if (optionMap.containsKey(question.getId())) { + EnumeratedProfileQuestion enumeratedProfileQuestion = new EnumeratedProfileQuestion(); + enumeratedProfileQuestion.setScaleSize(question.getScaleSize()); + enumeratedProfileQuestion.setOptions(optionMap.get(question.getId())); + enumeratedProfileQuestion.setQuestion(question.getQuestion()); + profileQuestions.add(enumeratedProfileQuestion); + }else { + profileQuestions.add(question); + } + } + + connect.close(); + return profileQuestions; + } + + public List<ProfileQuestionRelation> getProfileRelations() { + ArrayList<ProfileQuestionRelation>questionRelations = new ArrayList<>(); + connect.connect(); + String query = "SELCT * from profilequestionrelations"; + VereinfachtesResultSet vereinfachtesResultSet = connect.issueSelectStatement(query); + while (vereinfachtesResultSet.next()) { + int firstQuestionId = vereinfachtesResultSet.getInt("firstQuestionId"); + int secondQuestionId = vereinfachtesResultSet.getInt("secondQuestionId"); + String relation = vereinfachtesResultSet.getString("relation"); + + ProfileQuestion profileQuestion1 = new ProfileQuestion(firstQuestionId); + ProfileQuestion profileQuestion2 = new ProfileQuestion(secondQuestionId); + ProfileQuestionRelationType profileQuestionRelationType = ProfileQuestionRelationType.valueOf(relation); + questionRelations.add(new ProfileQuestionRelation(profileQuestion1, profileQuestion2, + profileQuestionRelationType)); + } + + connect.close(); + return questionRelations; + } + + public void persistProfileRelation(ProfileQuestion firstQuestion, ProfileQuestion secondQuestion) { + // TODO implement + connect.connect(); + + connect.close(); + } + +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestion.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestion.java new file mode 100644 index 0000000000000000000000000000000000000000..284014f8877032ebbc58facd36ebf6bf3610f8d1 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestion.java @@ -0,0 +1,46 @@ +package unipotsdam.gf.modules.group; + +public class ProfileQuestion { + private int id; + private int scaleSize; + private String question; + + public ProfileQuestion() { + } + + public ProfileQuestion(int id, int scaleSize, String question) { + this.id = id; + this.scaleSize = scaleSize; + this.question = question; + } + + public ProfileQuestion(int firstQuestionId) { + this.id= firstQuestionId; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getScaleSize() { + return scaleSize; + } + + public void setScaleSize(int scaleSize) { + this.scaleSize = scaleSize; + } + + public String getQuestion() { + return question; + } + + public void setQuestion(String question) { + this.question = question; + } + + //pr +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestionAnswer.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestionAnswer.java new file mode 100644 index 0000000000000000000000000000000000000000..cdfe200c938d7d4bdeaba0f2c0ff61e7a7f0f7ba --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestionAnswer.java @@ -0,0 +1,54 @@ +package unipotsdam.gf.modules.group; + +import unipotsdam.gf.modules.user.User; + +public class ProfileQuestionAnswer { + private ProfileQuestion question; + private Integer answerIndex; + // multiple is not implemented + private String selectedAnswer; + private User user; + + public ProfileQuestionAnswer( + ProfileQuestion question, Integer answerIndex, String selectedAnswer, User user) { + this.question = question; + this.answerIndex = answerIndex; + this.selectedAnswer = selectedAnswer; + this.user = user; + } + + public ProfileQuestionAnswer() { + } + + public ProfileQuestion getQuestion() { + return question; + } + + public void setQuestion(ProfileQuestion question) { + this.question = question; + } + + public Integer getAnswerIndex() { + return answerIndex; + } + + public void setAnswerIndex(Integer answerIndex) { + this.answerIndex = answerIndex; + } + + public String getSelectedAnswer() { + return selectedAnswer; + } + + public void setSelectedAnswer(String selectedAnswer) { + this.selectedAnswer = selectedAnswer; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestionRelation.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestionRelation.java new file mode 100644 index 0000000000000000000000000000000000000000..b067cc651a9a7ed8630529119d7611eb5c43ea36 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestionRelation.java @@ -0,0 +1,43 @@ +package unipotsdam.gf.modules.group; + +public class ProfileQuestionRelation { + private ProfileQuestion profileQuestion1; + private ProfileQuestion getProfileQuestion2; + private ProfileQuestionRelationType profileQuestionRelationType; + + public ProfileQuestionRelation( + ProfileQuestion profileQuestion1, ProfileQuestion getProfileQuestion2, + ProfileQuestionRelationType profileQuestionRelationType) { + this.profileQuestion1 = profileQuestion1; + this.getProfileQuestion2 = getProfileQuestion2; + this.profileQuestionRelationType = profileQuestionRelationType; + } + + public ProfileQuestionRelation() { + } + + public ProfileQuestion getProfileQuestion1() { + return profileQuestion1; + } + + public void setProfileQuestion1(ProfileQuestion profileQuestion1) { + this.profileQuestion1 = profileQuestion1; + } + + public ProfileQuestion getGetProfileQuestion2() { + return getProfileQuestion2; + } + + public void setGetProfileQuestion2(ProfileQuestion getProfileQuestion2) { + this.getProfileQuestion2 = getProfileQuestion2; + } + + public ProfileQuestionRelationType getProfileQuestionRelationType() { + return profileQuestionRelationType; + } + + public void setProfileQuestionRelationType( + ProfileQuestionRelationType profileQuestionRelationType) { + this.profileQuestionRelationType = profileQuestionRelationType; + } +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestionRelationType.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestionRelationType.java new file mode 100644 index 0000000000000000000000000000000000000000..7393a34ab11b372e5bc79037045adaa39f525609 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ProfileQuestionRelationType.java @@ -0,0 +1,17 @@ +package unipotsdam.gf.modules.group; + +/** + * This type indicates how groups are affected by the underlying variable of the item + * if it is homogenous, the groups can't have enough of it i.e. motivation + * + * If it is heteogenous, the groups should have contrasting members, i.e. more of a leader type but not too many + * alphas + * + * If it is nivelled, it is an attribute best avoided beause it could be mission critial, such as a depressed person. + * Here the groups should be greated in a way, that the problematic people are distributed in the groups + */ +public enum ProfileQuestionRelationType { + HOMOGENOUS, + HETEROGENOUS, + NIVELLED +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ScaledProfileQuestion.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ScaledProfileQuestion.java new file mode 100644 index 0000000000000000000000000000000000000000..5612a899ddee06c467cd523401e803748e2651a9 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/group/ScaledProfileQuestion.java @@ -0,0 +1,4 @@ +package unipotsdam.gf.modules.group; + +public class ScaledProfileQuestion extends ProfileQuestion { +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/groupfinding/service/GroupDAO.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/groupfinding/service/GroupDAO.java deleted file mode 100644 index ad14b1f6d9f4323985b643677521d98c455dc101..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/groupfinding/service/GroupDAO.java +++ /dev/null @@ -1,150 +0,0 @@ -/* -package unipotsdam.gf.modules.groupfinding.service; - -import org.apache.logging.log4j.util.Strings; -import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; -import unipotsdam.gf.modules.group.Group; -import unipotsdam.gf.mysql.MysqlConnect; -import unipotsdam.gf.mysql.VereinfachtesResultSet; -import unipotsdam.gf.util.ResultSetUtil; - -import javax.annotation.ManagedBean; -import javax.annotation.Resource; -import javax.inject.Inject; -import javax.inject.Singleton; -import java.util.*; - -@ManagedBean -@Resource -@Singleton -public class GroupDAO { - - private MysqlConnect connect; - - @Inject - public GroupDAO(MysqlConnect connect) { - this.connect = connect; - } - - public ArrayList<String> getStudentsInSameGroupAs(StudentIdentifier student) { - connect.connect(); - ArrayList<String> result = new ArrayList<>(); - Integer groupId; - String mysqlRequest1 = "SELECT groupId FROM `groupuser` WHERE `projectId`=? AND `studentId`=?"; - VereinfachtesResultSet vereinfachtesResultSet1 = - connect.issueSelectStatement(mysqlRequest1, student.getProjectName(), student.getUserEmail()); - vereinfachtesResultSet1.next(); - groupId = vereinfachtesResultSet1.getInt("groupId"); - String mysqlRequest2 = "SELECT * FROM `groupuser` WHERE `groupId`=?"; - VereinfachtesResultSet vereinfachtesResultSet2 = - connect.issueSelectStatement(mysqlRequest2, groupId); - boolean next2 = vereinfachtesResultSet2.next(); - while (next2) { - String peer = vereinfachtesResultSet2.getString("studentId"); - if (!peer.equals(student.getUserEmail())) - result.add(peer); - next2 = vereinfachtesResultSet2.next(); - } - connect.close(); - return result; - } - - public void persist(Group group) { - connect.connect(); - List<Group> existingGroups = getExistingEntriesOfGroups(group.get()); - if (Objects.isNull(group.getChatRoomId())) { - group.setChatRoomId(""); - } - String mysqlRequestGroup = "INSERT INTO groups (`projectId`,`chatRoomId`) values (?,?)"; - connect.issueInsertOrDeleteStatement(mysqlRequestGroup, group.getProjectId(), group.getChatRoomId()); - - List<Group> existingGroupsWithNewEntry = getExistingEntriesOfGroups(group.getProjectId()); - existingGroupsWithNewEntry.removeAll(existingGroups); - group.setId(existingGroupsWithNewEntry.get(0).getId()); - for (User groupMember : group.getMembers()) { - String mysqlRequest2 = "INSERT INTO groupuser (`studentId`, `projectId`, `groupId`) values (?,?,?)"; - connect.issueInsertOrDeleteStatement(mysqlRequest2, groupMember.getEmail(), group.getProjectId(), group.getId()); - } - connect.close(); - - } - - public void update(Group group) { - connect.connect(); - String mysqlRequest = "UPDATE groups SET projectId = ?, chatRoomId = ? where id = ?"; - connect.issueUpdateStatement(mysqlRequest, group.getProjectId(), group.getChatRoomId(), group.getId()); - connect.close(); - // TODO: implement update of groupuser if needed later (if member list need to be updated) - } - - public void clearChatRoomIdOfGroup(String chatRoomId) { - connect.connect(); - String mysqlRequest = "update groups SET chatRoomId = ? where chatRoomId = ?"; - connect.issueUpdateStatement(mysqlRequest, "", chatRoomId); - connect.close(); - } - - public Boolean exists(Group group) { - List<Group> existingGroups = getGroupsByProjectId(group.getProjectId()); - return existingGroups.contains(group); - } - - public void addGroupMember(User groupMember, int groupId) { - // TODO: implement - } - - public void deleteGroupMember(User groupMember, int groupId) { - // TODO: implement - } - - public List<Group> getGroupsByProjectId(String projectId) { - connect.connect(); - String mysqlRequest = "SELECT * FROM groups g " + - "JOIN groupuser gu ON g.id=gu.groupId " + "JOIN users u ON gu.studentId=u.email " + - "where g.projectId = ?"; - VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, projectId); - if (Objects.isNull(vereinfachtesResultSet)) { - connect.close(); - return Collections.emptyList(); - } - HashMap<Integer, Group> groupHashMap = new HashMap<>(); - while (vereinfachtesResultSet.next()) { - fillGroupFromResultSet(vereinfachtesResultSet, groupHashMap); - } - ArrayList<Group> groups = new ArrayList<>(); - groupHashMap.forEach((key, group) -> groups.add(group)); - connect.close(); - return groups; - } - - - - private void fillGroupFromResultSet(VereinfachtesResultSet vereinfachtesResultSet, HashMap<Integer, Group> existingGroups) { - int id = vereinfachtesResultSet.getInt("id"); - if (existingGroups.containsKey(id)) { - existingGroups.get(id).addMember(ResultSetUtil.getUserFromResultSet(vereinfachtesResultSet)); - } else { - String projectId = vereinfachtesResultSet.getString("projectId"); - User user = ResultSetUtil.getUserFromResultSet(vereinfachtesResultSet); - String chatRoomId = vereinfachtesResultSet.getString("chatRoomId"); - ArrayList<User> userList = new ArrayList<>(Collections.singletonList(user)); - Group group = new Group(id, userList, projectId, chatRoomId); - existingGroups.put(id, group); - } - } - - private List<Group> getExistingEntriesOfGroups(String projectId) { - String mysqlExistingGroup = "SELECT * FROM groups WHERE projectId = ?"; - VereinfachtesResultSet resultSet = connect.issueSelectStatement(mysqlExistingGroup, projectId); - ArrayList<Group> existingGroups = new ArrayList<>(); - while (resultSet.next()) { - int id = resultSet.getInt("id"); - Group existingGroup = new Group(id, projectId); - existingGroups.add(existingGroup); - } - return existingGroups; - } - -} -*/ diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/model/Journal.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/model/Journal.java index 369f6a74bada4eff2fc91154b607c70ddbc966e9..e91cfc9e2de6786cf119f3c0ae4a6ca7350f5e4e 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/model/Journal.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/model/Journal.java @@ -2,7 +2,7 @@ package unipotsdam.gf.modules.journal.model; import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import java.util.Date; @@ -14,7 +14,7 @@ import static unipotsdam.gf.util.MarkdownUtils.convertMarkdownToHtml; public class Journal { private String id; - private StudentIdentifier userNameentifier; + private StudentIdentifier studentIdentifier; private String entryHTML; private String entryMD; private long timestamp; @@ -24,9 +24,9 @@ public class Journal { public Journal() {} - public Journal(String id, StudentIdentifier userNameentifier, String entryMD, Visibility visibility, Category category) { + public Journal(String id, StudentIdentifier studentIdentifier, String entryMD, Visibility visibility, Category category) { this.id = id; - this.userNameentifier = userNameentifier; + this.studentIdentifier = studentIdentifier; entryHTML = convertMarkdownToHtml(entryMD); this.entryMD = entryMD; this.visibility = visibility; @@ -35,9 +35,9 @@ public class Journal { timestamp = new Date().getTime(); } - public Journal(String id, StudentIdentifier userNameentifier, String entryMD, long timestamp, Visibility visibility, Category category, boolean open) { + public Journal(String id, StudentIdentifier studentIdentifier, String entryMD, long timestamp, Visibility visibility, Category category, boolean open) { this.id = id; - this.userNameentifier = userNameentifier; + this.studentIdentifier = studentIdentifier; entryHTML = convertMarkdownToHtml(entryMD); this.entryMD = entryMD; this.timestamp = timestamp; @@ -68,11 +68,11 @@ public class Journal { } public StudentIdentifier getStudentIdentifier() { - return userNameentifier; + return studentIdentifier; } public void setStudentIdentifier(StudentIdentifier userNameentifier) { - this.userNameentifier = userNameentifier; + this.studentIdentifier = userNameentifier; } public String getEntryHTML() { @@ -111,7 +111,7 @@ public class Journal { public String toString() { return "Journal{" + "id=" + id + - ", userNameentifier=" + userNameentifier + + ", studentIdentifier=" + studentIdentifier + ", entryHTML='" + entryHTML + '\'' + ", entryMD='" + entryMD + '\'' + ", timestamp=" + timestamp + diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/model/dao/JournalDAOImpl.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/model/dao/JournalDAOImpl.java index d517ee419162604e04da1e91ccf43ca664e667d5..27a35424d595dedc57be04c5ef32300f188eac3a 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/model/dao/JournalDAOImpl.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/model/dao/JournalDAOImpl.java @@ -15,10 +15,10 @@ import java.util.UUID; public class JournalDAOImpl implements JournalDAO { @Inject - MysqlConnect connection; + private MysqlConnect connection; - - JournalUtils utils; + @Inject + private JournalUtils utils; @Override public void createJournal(Journal journal) { @@ -32,7 +32,7 @@ public class JournalDAOImpl implements JournalDAO { connection.connect(); // build and execute request - String request = "INSERT INTO journals (`id`, `userName`, `projectName`, `text`, `visibility`,`category`, `open` ) VALUES (?,?,?,?,?,?,?);"; + String request = "INSERT INTO journals (`id`, `userEmail`, `projectName`, `text`, `visibility`,`category`, `open` ) VALUES (?,?,?,?,?,?,?);"; connection.issueInsertOrDeleteStatement(request, uuid, journal.getStudentIdentifier().getUserEmail(), journal.getStudentIdentifier().getProjectName(), journal.getEntryMD(), journal.getVisibility(), journal.getCategory(), true); diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/util/JournalUtils.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/util/JournalUtils.java index 505c756dfe401c87fa8c0d54c0a4d63351497a70..31573feebf208371f14f521945cc984e5c523412 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/util/JournalUtils.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/util/JournalUtils.java @@ -5,7 +5,7 @@ import org.slf4j.LoggerFactory; import unipotsdam.gf.mysql.MysqlConnect; import unipotsdam.gf.mysql.VereinfachtesResultSet; import unipotsdam.gf.modules.journal.model.Visibility; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import javax.inject.Inject; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/view/JournalView.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/view/JournalView.java index f60a8730f1274faf0e13041157b63bf6e04ccdff..ad9a0b14aa226674350da6defe41cb1af95d16a6 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/view/JournalView.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/journal/view/JournalView.java @@ -7,7 +7,12 @@ import unipotsdam.gf.modules.journal.model.Journal; import unipotsdam.gf.modules.journal.model.JournalFilter; import unipotsdam.gf.modules.journal.service.JournalService; import unipotsdam.gf.modules.journal.service.JournalServiceImpl; +import unipotsdam.gf.modules.project.Management; +import unipotsdam.gf.modules.project.ProjectDAO; +import unipotsdam.gf.process.ProjectCreationProcess; +import unipotsdam.gf.session.GFContexts; +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.GET; @@ -30,8 +35,10 @@ import java.util.ArrayList; @Path("/journal") public class JournalView { + @Inject + private JournalService journalService; + private final Logger log = LoggerFactory.getLogger(JournalView.class); - private final JournalService journalService = new JournalServiceImpl(); /** * Returns a specific Journal diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/Management.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/Management.java index cb65a64f4052627036766210227c72ec3d3afd77..822440510f6a15407265e69188f0128a36713473 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/Management.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/Management.java @@ -93,7 +93,9 @@ public interface Management { List<String> getProjects(String userEmail); - List<String> getProjectsStudent(String studentToken); + List<Project> getProjectsStudent(String studentToken); + + List<Project> getAllProjects(); String saveProfilePicture(FileInputStream fis, String userName); diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ManagementImpl.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ManagementImpl.java index 03327bb3d4ce66bf062a21286c823c9af9ccf71d..4780478319ec58c9501b12d59ae682c545a4d39a 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ManagementImpl.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ManagementImpl.java @@ -7,6 +7,7 @@ import unipotsdam.gf.modules.user.User; import unipotsdam.gf.modules.user.UserDAO; import unipotsdam.gf.modules.user.UserInterests; import unipotsdam.gf.modules.user.UserProfile; +import unipotsdam.gf.process.phases.Phase; import unipotsdam.gf.util.ResultSetUtil; import unipotsdam.gf.modules.group.GroupDAO; @@ -73,7 +74,7 @@ public class ManagementImpl implements Management { @Override public void update(User user) { - userDAO.update(user); + userDAO.updateRocketChatUserName(user); } @Override @@ -153,21 +154,33 @@ public class ManagementImpl implements Management { } @Override - public List<String> getProjectsStudent(String studentEmail) { + public List<Project> getProjectsStudent(String studentEmail) { if (studentEmail == null) { return null; } connect.connect(); String mysqlRequest = - "SELECT projectName FROM projectuser WHERE useremail=?"; - - //49c6eeda-62d2-465e-8832-dc2db27e760c - - List<String> result = new ArrayList<>(); + "SELECT projectName FROM projectuser WHERE userEmail=?"; + List<Project> result = new ArrayList<>(); VereinfachtesResultSet vereinfachtesResultSet = connect.issueSelectStatement(mysqlRequest, studentEmail); while (vereinfachtesResultSet.next()) { String project = vereinfachtesResultSet.getString("projectName"); - result.add(project); + result.add(projectDAO.getProjectByName(project)); + } + connect.close(); + return result; + } + + @Override + public List<Project> getAllProjects() { + connect.connect(); + String mysqlRequest = + "SELECT name FROM projects WHERE phase=?"; + List<Project> result = new ArrayList<>(); + VereinfachtesResultSet vereinfachtesResultSet = connect.issueSelectStatement(mysqlRequest, Phase.GroupFormation); + while (vereinfachtesResultSet.next()) { + String project = vereinfachtesResultSet.getString("name"); + result.add(projectDAO.getProjectByName(project)); } connect.close(); return result; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/Project.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/Project.java index f8a590c389bd032f6db5b5a11ce53929b914006a..a82839ac319587c637790cb459eb6bc46a6e20e5 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/Project.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/Project.java @@ -17,26 +17,37 @@ public class Project { private Long timecreated; //timestamp macht zu viele Probleme // the id of the authorEmail (not the token) private String authorEmail; - private String adminPassword; private Phase phase; private String[] tags; + private String description; public Project() { tags = new String[0]; } + public Project(String name, String password, Boolean active, String author, String[] tags) { + this.name = name; + this.password = password; + this.active = active; + this.authorEmail = author; + this.timecreated = System.currentTimeMillis(); + // default starting at course creation if new + this.setPhase(Phase.GroupFormation); + this.tags = tags; + } public Project( - String name, String password, Boolean active, Long timecreated, String authorEmail, - String adminPassword, Phase phase, String[] tags) { + String name, String password, Boolean active, + Long timecreated, String authorEmail, + Phase phase, String[] tags, String description) { this.name = name; this.password = password; this.active = active; this.authorEmail = authorEmail; this.timecreated = timecreated; - this.adminPassword = adminPassword; this.phase = phase; this.tags = tags; + this.description = description; } public Project(String projectName, String authorEmail) { @@ -77,16 +88,6 @@ public class Project { this.authorEmail = authorEmail; } - public String getAdminPassword() { - return adminPassword; - } - - public void setAdminPassword(String adminPassword) { - this.adminPassword = adminPassword; - } - - - public Phase getPhase() { return phase; } @@ -136,10 +137,17 @@ public class Project { sb.append(", active=").append(active); sb.append(", timecreated=").append(timecreated); sb.append(", authorEmail='").append(authorEmail).append('\''); - sb.append(", adminPassword='").append(adminPassword).append('\''); sb.append(", phase=").append(phase); sb.append(", tags=").append(Arrays.toString(tags)); sb.append('}'); return sb.toString(); } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } } \ No newline at end of file diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectConfiguration.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectConfiguration.java index 72cef067ec0115d3701ff3d5558d7ec910044752..89672442e96084860e520e7cd191acf502fd4af5 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectConfiguration.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectConfiguration.java @@ -3,7 +3,7 @@ package unipotsdam.gf.modules.project; import unipotsdam.gf.process.phases.Phase; import unipotsdam.gf.modules.assessment.AssessmentMechanism; import unipotsdam.gf.modules.group.GroupFormationMechanism; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import java.util.HashMap; @@ -12,13 +12,13 @@ public class ProjectConfiguration { private HashMap<Phase, Boolean> phasesSelected; private HashMap<Category, Boolean> criteriaSelected; - private HashMap<AssessmentMechanism, Boolean> assessmentMechanismSelected; + private AssessmentMechanism assessmentMechanismSelected; private GroupFormationMechanism groupMechanismSelected; public ProjectConfiguration( HashMap<Phase, Boolean> phasesSelected, HashMap<Category, Boolean> criteriaSelected, - HashMap<AssessmentMechanism, Boolean> assessmentMechanismSelected, + AssessmentMechanism assessmentMechanismSelected, GroupFormationMechanism groupMechanismSelected) { this.phasesSelected = phasesSelected; this.criteriaSelected = criteriaSelected; @@ -43,12 +43,12 @@ public class ProjectConfiguration { this.criteriaSelected = criteriaSelected; } - public HashMap<AssessmentMechanism, Boolean> getAssessmentMechanismSelected() { + public AssessmentMechanism getAssessmentMechanismSelected() { return assessmentMechanismSelected; } public void setAssessmentMechanismSelected( - HashMap<AssessmentMechanism, Boolean> assessmentMechanismSelected) { + AssessmentMechanism assessmentMechanismSelected) { this.assessmentMechanismSelected = assessmentMechanismSelected; } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectConfigurationDAO.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectConfigurationDAO.java index 64aba706918af600cc007c64dde70b1391691be5..439f49c6bf1afe00c25f7ac58fc621187265f226 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectConfigurationDAO.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectConfigurationDAO.java @@ -1,11 +1,11 @@ package unipotsdam.gf.modules.project; +import unipotsdam.gf.modules.annotation.model.Category; +import unipotsdam.gf.modules.assessment.AssessmentMechanism; +import unipotsdam.gf.modules.group.GroupFormationMechanism; import unipotsdam.gf.mysql.MysqlConnect; import unipotsdam.gf.mysql.VereinfachtesResultSet; import unipotsdam.gf.process.phases.Phase; -import unipotsdam.gf.modules.assessment.AssessmentMechanism; -import unipotsdam.gf.modules.group.GroupFormationMechanism; -import unipotsdam.gf.modules.feedback.Category; import javax.inject.Inject; import java.util.HashMap; @@ -31,7 +31,7 @@ public class ProjectConfigurationDAO { String projectName = project.getName(); String categoryName = category.name(); String mysqlRequest = "insert INTO categoriesSelected (`projectName`,`categorySelected`) VALUES (?,?)"; - connect.issueInsertOrDeleteStatement(mysqlRequest, projectName, categoryName ); + connect.issueInsertOrDeleteStatement(mysqlRequest, projectName, categoryName); } } @@ -46,27 +46,24 @@ public class ProjectConfigurationDAO { } // persist GroupFinding - GroupFormationMechanism groupFindingMechanism = + GroupFormationMechanism groupFindingMechanism = projectConfiguration.getGroupMechanismSelected(); - if (groupFindingMechanism != null) { - String mysqlRequest = - "insert INTO groupfindingMechanismSelected (`projectName`,`gfmSelected`) VALUES (?,?)"; - connect.issueInsertOrDeleteStatement(mysqlRequest, project.getName(), groupFindingMechanism.name()); - } - + if (groupFindingMechanism != null) { + String mysqlRequest = + "insert INTO groupfindingMechanismSelected (`projectName`,`gfmSelected`) VALUES (?,?)"; + connect.issueInsertOrDeleteStatement(mysqlRequest, project.getName(), groupFindingMechanism.name()); + } // persist assessmentMechanismSelected - HashMap<AssessmentMechanism, Boolean> assessmentMechanismsSelected = + AssessmentMechanism assessmentMechanismsSelected = projectConfiguration.getAssessmentMechanismSelected(); - for (AssessmentMechanism assessmentMechanism : assessmentMechanismsSelected.keySet()) { - Boolean asmSelected = assessmentMechanismsSelected.get(assessmentMechanism); - if (asmSelected != null && asmSelected) { - String mysqlRequest = - "insert INTO assessmentMechanismSelected (`projectName`,`amSelected`) VALUES (?,?)"; - connect.issueInsertOrDeleteStatement(mysqlRequest, project.getName(), assessmentMechanism.name()); - } + + if (assessmentMechanismsSelected != null) { + String mysqlRequest = + "insert INTO assessmentMechanismSelected (`projectName`,`amSelected`) VALUES (?,?)"; + connect.issueInsertOrDeleteStatement(mysqlRequest, project.getName(), assessmentMechanismsSelected.name()); } connect.close(); } @@ -80,16 +77,15 @@ public class ProjectConfigurationDAO { HashMap<Category, Boolean> categorySelected = getSelectionFromTable(connect, Category.class, project, "categoriesSelected"); - - HashMap<AssessmentMechanism, Boolean> asmSelected = - getSelectionFromTable(connect, AssessmentMechanism.class, project, "assessmentMechanismSelected"); - - + AssessmentMechanism asmSelected = AssessmentMechanism.PEER_ASSESSMENT; + HashMap<AssessmentMechanism, Boolean> aMBHM = getSelectionFromTable(connect, AssessmentMechanism.class, project, "assessmentMechanismSelected"); + for (AssessmentMechanism am : aMBHM.keySet()){ + asmSelected =am; + } HashMap<GroupFormationMechanism, Boolean> groupfindingMechanismSelected = getSelectionFromTable(connect, GroupFormationMechanism.class, project, "groupfindingMechanismSelected"); - GroupFormationMechanism gfmSelected = null; for (GroupFormationMechanism groupFormationMechanism : groupfindingMechanismSelected.keySet()) { @@ -100,7 +96,7 @@ public class ProjectConfigurationDAO { connect.close(); ProjectConfiguration projectConfiguration = new ProjectConfiguration(projectPhasesSelected, categorySelected, - asmSelected,gfmSelected); + asmSelected, gfmSelected); return projectConfiguration; } @@ -124,7 +120,7 @@ public class ProjectConfigurationDAO { VereinfachtesResultSet vereinfachtesResultSet = connect.issueSelectStatement(mysqlRequest, id); HashMap<T, Boolean> projectPhaseBoolean = new HashMap<>(); while (!vereinfachtesResultSet.isLast()) { - Boolean next = vereinfachtesResultSet.next(); + boolean next = vereinfachtesResultSet.next(); if (next) { String phaseSelected = vereinfachtesResultSet.getObject(2).toString(); T phaseSelected1 = Enum.valueOf(selectionclass, phaseSelected); diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectDAO.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectDAO.java index b26ea42c94987adb6519259f2f4316fe6200f335..ee89b87810dfb6e36a3563db3f013ade0cc88a6a 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectDAO.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectDAO.java @@ -12,15 +12,22 @@ import javax.annotation.ManagedBean; import javax.annotation.Resource; import javax.inject.Inject; import javax.inject.Singleton; +import java.security.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.TimeZone; + +import static java.util.TimeZone.getDefault; @ManagedBean @Resource @Singleton public class ProjectDAO { + @Inject private MysqlConnect connect; @Inject @@ -37,12 +44,13 @@ public class ProjectDAO { public void persist(Project project) { if (!exists(project)) { - + java.sql.Timestamp timestamp = new java.sql.Timestamp(project.getTimecreated()); connect.connect(); String mysqlRequest = - "INSERT INTO projects (`name`, `password`, `active`, `timecreated`, `author`, " + "`adminPassword`, `phase`) values (?,?,?,?,?,?,?)"; + "INSERT INTO projects (`name`, `password`, `active`, `timecreated`, `author`, " + "`phase`) " + + "values (?,?,?,?,?,?,?)"; connect.issueInsertOrDeleteStatement(mysqlRequest, project.getName(), project.getPassword(), - project.isActive(), project.getTimecreated(), project.getAuthorEmail(), project.getAdminPassword(), + project.isActive(), project.getTimecreated(), project.getAuthorEmail(), project.getPhase() == null ? Phase.GroupFormation : project.getPhase()); connect.close(); @@ -75,9 +83,9 @@ public class ProjectDAO { public Boolean exists(Project project) { Boolean result; connect.connect(); - String mysqlRequest = "SELECT * FROM projects where name = ? and adminPassword = ?"; + String mysqlRequest = "SELECT * FROM projects where name = ?"; VereinfachtesResultSet vereinfachtesResultSet = - connect.issueSelectStatement(mysqlRequest, project.getName(), project.getAdminPassword()); + connect.issueSelectStatement(mysqlRequest, project.getName()); if (vereinfachtesResultSet == null) { return false; } @@ -115,11 +123,10 @@ public class ProjectDAO { boolean active = vereinfachtesResultSet.getBoolean("active"); long timestamp = vereinfachtesResultSet.getLong("timecreated"); String author = vereinfachtesResultSet.getString("author"); - String adminPassword = vereinfachtesResultSet.getString("adminpassword"); String phase = vereinfachtesResultSet.getString("phase"); + String description = vereinfachtesResultSet.getString("description"); - - return new Project(id, password, active, timestamp, author, adminPassword, Phase.valueOf(phase), null); + return new Project(id, password, active, timestamp, author, Phase.valueOf(phase), null, description); } public java.util.List<String> getTags(Project project) { diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectView.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectView.java index ab8525d346360372a68f0e3ef601f96bd5327f8a..a3a4684d33e0cb82ce4db65fabb412d1abc4521b 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectView.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/project/ProjectView.java @@ -1,8 +1,13 @@ package unipotsdam.gf.modules.project; +import com.sun.org.apache.xpath.internal.operations.Bool; import unipotsdam.gf.process.ProjectCreationProcess; import unipotsdam.gf.process.tasks.TaskDAO; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; import unipotsdam.gf.modules.user.User; +import unipotsdam.gf.session.GFContext; +import unipotsdam.gf.process.ProjectCreationProcess; import unipotsdam.gf.session.GFContexts; import javax.annotation.ManagedBean; @@ -13,6 +18,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import java.io.IOException; import java.net.URISyntaxException; +import java.util.List; @ManagedBean @@ -32,11 +38,12 @@ public class ProjectView { @Inject private ProjectCreationProcess projectCreationProcess; - @PUT + @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) @Path("/create") - public void createProject(@Context HttpServletRequest req, Project project) throws URISyntaxException, IOException { + public String createProject(@Context HttpServletRequest req, Project project) + throws IOException, RocketChatDownException, UserDoesNotExistInRocketChatException { String userEmail = gfContexts.getUserEmail(req); User user = iManagement.getUserByEmail(userEmail); assert user != null; @@ -44,6 +51,24 @@ public class ProjectView { throw new IOException("NO user with this email exists in db"); } projectCreationProcess.createProject(project, user); + return "success"; + } + + @POST + @Path("/delete/project/{projectName}") + public void deleteProject(@Context HttpServletRequest req, @PathParam("projectName") String projectName) + throws URISyntaxException, IOException, RocketChatDownException, UserDoesNotExistInRocketChatException { + String userEmail1 = gfContexts.getUserEmail(req); + User user = iManagement.getUserByEmail(userEmail1); + Boolean isStudent= user.getStudent(); + String userEmail = gfContexts.getUserEmail(req); + Project project = projectDAO.getProjectByName(projectName); + if (!isStudent){ + if (project.getAuthorEmail().equals(userEmail)){ + projectCreationProcess.deleteProject(project); + } + } + } @GET @@ -59,24 +84,26 @@ public class ProjectView { @GET @Produces(MediaType.APPLICATION_JSON) @Path("/all/student/{studentEmail}") - public String[] getProjectsStudent( + public java.util.List<Project> getProjectsStudent( @PathParam("studentEmail") String studentEmail) { - return iManagement.getProjectsStudent(studentEmail).toArray(new String[0]); + return iManagement.getProjectsStudent(studentEmail); } + @GET @Consumes(MediaType.TEXT_PLAIN) @Produces(MediaType.TEXT_PLAIN) @Path("/login/{projectName}") - public String register(@Context HttpServletRequest req, @PathParam("projectName") String projectName, - @QueryParam("password") String - password) throws IOException { + public String register( + @Context HttpServletRequest req, @PathParam("projectName") String projectName, + @QueryParam("password") String password) + throws IOException, RocketChatDownException, UserDoesNotExistInRocketChatException { User user = gfContexts.getUserFromSession(req); Project project = projectDAO.getProjectByName(projectName); - if (project == null){ + if (project == null) { return "project missing"; } - if (!project.getPassword().equals(password) ) { + if (!project.getPassword().equals(password)) { return "wrong password"; } @@ -97,5 +124,4 @@ public class ProjectView { } - } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/researchreport/DummyResearchReportManagement.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/researchreport/DummyResearchReportManagement.java index 3d5e4fcb2bb0b9157b2803862318b9b249731acc..179df20cf66de34a971a7078224637c0bcb348d4 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/researchreport/DummyResearchReportManagement.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/researchreport/DummyResearchReportManagement.java @@ -33,7 +33,7 @@ public class DummyResearchReportManagement implements ResearchReportManagement { // uploading dossiers to feedback if (DummyResearchReportCounter.feedbackTasksNotAssigned) { DummyResearchReportCounter.feedbackTasksNotAssigned = false; - feedback.assignFeedbackTasks(project); + //feedback.specifyFeedbackTasks(project); } return factory.manufacturePojo(ResearchReport.class).getId(); } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/controller/SubmissionController.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/controller/SubmissionController.java index b5aeb64848d9b4dda47e5c6ba8e3c79b50bfb708..4f578ce9e93b59b9c09abc52aca1e5e7eb61a63b 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/controller/SubmissionController.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/controller/SubmissionController.java @@ -12,19 +12,19 @@ import unipotsdam.gf.modules.user.UserDAO; import unipotsdam.gf.mysql.MysqlConnect; import unipotsdam.gf.mysql.VereinfachtesResultSet; import unipotsdam.gf.interfaces.ISubmission; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import unipotsdam.gf.modules.submission.model.FullSubmission; import unipotsdam.gf.modules.submission.model.FullSubmissionPostRequest; import unipotsdam.gf.modules.submission.model.SubmissionPart; import unipotsdam.gf.modules.submission.model.SubmissionPartBodyElement; import unipotsdam.gf.modules.submission.model.SubmissionPartPostRequest; import unipotsdam.gf.modules.submission.model.SubmissionProjectRepresentation; -import unipotsdam.gf.process.constraints.ConstraintsImpl; -import unipotsdam.gf.process.phases.Phase; import unipotsdam.gf.process.progress.HasProgress; import unipotsdam.gf.process.progress.ProgressData; import unipotsdam.gf.process.tasks.FeedbackTaskData; import unipotsdam.gf.process.tasks.ParticipantsCount; +import unipotsdam.gf.process.tasks.Progress; +import unipotsdam.gf.process.tasks.TaskName; import javax.inject.Inject; import java.util.ArrayList; @@ -576,7 +576,7 @@ public class SubmissionController implements ISubmission, HasProgress { */ public void markAsFinal(FullSubmission fullSubmission) { connection.connect(); - String query = "update fullsubmissions set finalized = ? where id = ?"; + String query = "updateRocketChatUserName fullsubmissions set finalized = ? where id = ?"; connection.issueUpdateStatement(query, 1, fullSubmission.getId()); connection.close(); } @@ -591,7 +591,7 @@ public class SubmissionController implements ISubmission, HasProgress { */ public void updateFullSubmission(User submissionOwner, User feedbackGiver) { connection.connect(); - String query = "update fullsubmissions set feedbackUser = ? where user = ?"; + String query = "updateRocketChatUserName fullsubmissions set feedbackUser = ? where user = ?"; connection.issueUpdateStatement(query, feedbackGiver.getEmail(), submissionOwner.getEmail()); connection.close(); // TODO implement linking submission with group @@ -610,7 +610,7 @@ public class SubmissionController implements ISubmission, HasProgress { if (vereinfachtesResultSet.next()) { String submissionId = vereinfachtesResultSet.getString("id"); String projectName = vereinfachtesResultSet.getString("projectName"); - Category category = Category.RECHERCHE; + Category category = Category.TITEL; //todo: instead hard coding, look up which category is next to annotate FullSubmission fullSubmission = new FullSubmission(submissionId); fullSubmission.setProjectName(projectName); connection.close(); @@ -640,7 +640,7 @@ public class SubmissionController implements ISubmission, HasProgress { // the number of completed dossiers progressData.setNumberOfCompletion(getFinalizedDossiersCount(project)); - // the number of dossiers needed relativ to the group or user count + // the number of dossiers needed relative to the group or user count progressData.setNumberNeeded(dossiersNeeded(project)); List<User> strugglersWithSubmission = getStrugglersWithSubmission(project); progressData.setUsersMissing(strugglersWithSubmission); @@ -680,9 +680,9 @@ public class SubmissionController implements ISubmission, HasProgress { switch (groupFormationMechanism) { case SingleUser: List<User> usersInProject = userDAO.getUsersByProjectName(project.getName()); - List<User> usersHavingGivenFeedback = getAllUsersWithFeedbackGiven(project); + List<User> usersHaveGivenFeedback = getAllUsersWithDossierUploaded(project); for (User user : usersInProject) { - if (!usersHavingGivenFeedback.contains(user)) { + if (!usersHaveGivenFeedback.contains(user)) { struggles.add(user); } } @@ -694,7 +694,7 @@ public class SubmissionController implements ISubmission, HasProgress { return struggles; } - public List<User> getAllUsersWithFeedbackGiven(Project project) { + public List<User> getAllUsersWithDossierUploaded(Project project) { List<User> result = new ArrayList<>(); connection.connect(); String query = "select * from fullsubmissions where projectName = ?"; @@ -706,4 +706,18 @@ public class SubmissionController implements ISubmission, HasProgress { connection.close(); return result; } + + public List<User> getAllUsersWithFinalizedFeedback(Project project){ + List<User> result = new ArrayList<>(); + connection.connect(); + String query = "select * from tasks where projectName = ? and taskName = ? and progress=?"; + VereinfachtesResultSet vereinfachtesResultSet = connection.issueSelectStatement(query, + project.getName(), TaskName.GIVE_FEEDBACK, Progress.FINISHED); + + while (vereinfachtesResultSet.next()) { + result.add(userDAO.getUserByEmail(vereinfachtesResultSet.getString("userEmail"))); + } + connection.close(); + return result; + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionPart.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionPart.java index 4d0ff854083879aa717967060aef6d031890683e..c918a958a61d7798dd2e57cc7d5f855373f6081c 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionPart.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionPart.java @@ -1,6 +1,6 @@ package unipotsdam.gf.modules.submission.model; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import java.util.ArrayList; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionPartPostRequest.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionPartPostRequest.java index d0dc0f3357bb2afe88a682a78eb7863acc7db3ca..1dac58a196cb0455417072613fa4b1c352d4689e 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionPartPostRequest.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionPartPostRequest.java @@ -1,6 +1,6 @@ package unipotsdam.gf.modules.submission.model; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import java.util.ArrayList; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionProjectRepresentation.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionProjectRepresentation.java index 5063544131eb0f80565db531f43cabbdc550928c..228b29e0227d7aa0268e2e993d464e6f32800405 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionProjectRepresentation.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/model/SubmissionProjectRepresentation.java @@ -1,6 +1,6 @@ package unipotsdam.gf.modules.submission.model; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; public class SubmissionProjectRepresentation { diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/view/SubmissionService.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/view/SubmissionService.java index a669d28e9956283e5381beafcce23696ff8febab..b4310b14678daa346f954b1265f4681f5450c9c4 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/view/SubmissionService.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/submission/view/SubmissionService.java @@ -1,6 +1,6 @@ package unipotsdam.gf.modules.submission.view; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.modules.submission.controller.SubmissionController; import unipotsdam.gf.modules.submission.model.FullSubmission; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/HiddenUserTag.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/HiddenUserTag.java index 836fd02959063f9bacc344ad6715286f645983e3..61e1e4afc5cf705bb654654fee6b5308b45caf10 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/HiddenUserTag.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/HiddenUserTag.java @@ -9,7 +9,7 @@ import javax.servlet.jsp.tagext.SimpleTagSupport; import java.io.IOException; /** - * implemented while porting the login page. It might be useful to have a hidden user field on the page in order to + * implemented while porting the register page. It might be useful to have a hidden user field on the page in order to * manipulate the user data with jquery */ @ManagedBean diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/User.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/User.java index 6a7645073dd5f17761baf74503528bf6f6663327..2a877f7d7e042e7f0465c1d0acde24daa7c80e9f 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/User.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/User.java @@ -1,5 +1,6 @@ package unipotsdam.gf.modules.user; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Objects; @@ -13,10 +14,9 @@ public class User { private String name; private String password; private String email; + // the speaking name must unique in rocket chat private String rocketChatUsername; - private String rocketChatAuthToken; - private String rocketChatPersonalAccessToken; - private String rocketChatUserId; + private Boolean isStudent; public User() { @@ -24,28 +24,15 @@ public class User { - public User(String name, String password, String email, String rocketChatUsername, String rocketChatPersonalAccessToken, String rocketChatUserId) { + public User(String name, String password, String email, String rocketChatUsername, Boolean isStudent) { this.name = name; this.password = password; this.email = email; this.rocketChatUsername = rocketChatUsername; - this.rocketChatPersonalAccessToken = rocketChatPersonalAccessToken; - this.rocketChatUserId = rocketChatUserId; - + this.setStudent(isStudent); } - public User(String name, String password, String email, String rocketChatUsername, - String rocketChatAuthToken, String rocketChatPersonalAccessToken, String rocketChatUserId, - Boolean isStudent) { - this.name = name; - this.password = password; - this.email = email; - this.rocketChatUsername = rocketChatUsername; - this.rocketChatAuthToken = rocketChatAuthToken; - this.rocketChatPersonalAccessToken = rocketChatPersonalAccessToken; - this.rocketChatUserId = rocketChatUserId; - this.isStudent = isStudent; - } + public User(String authorEmail) { this.email = authorEmail; @@ -67,6 +54,7 @@ public class User { return name; } + @JsonIgnore public String getPassword() { return password; } @@ -91,21 +79,7 @@ public class User { isStudent = student; } - public String getRocketChatUserId() { - return rocketChatUserId; - } - - public void setRocketChatUserId(String rocketChatUserId) { - this.rocketChatUserId = rocketChatUserId; - } - - public String getRocketChatAuthToken() { - return rocketChatAuthToken; - } - public void setRocketChatAuthToken(String rocketChatAuthToken) { - this.rocketChatAuthToken = rocketChatAuthToken; - } public String getRocketChatUsername() { return rocketChatUsername; @@ -115,13 +89,7 @@ public class User { this.rocketChatUsername = rocketChatUsername; } - public String getRocketChatPersonalAccessToken() { - return rocketChatPersonalAccessToken; - } - public void setRocketChatPersonalAccessToken(String rocketChatPersonalAccessToken) { - this.rocketChatPersonalAccessToken = rocketChatPersonalAccessToken; - } @Override public String toString() { @@ -130,9 +98,6 @@ public class User { ", password='" + password + '\'' + ", email='" + email + '\'' + ", rocketChatUsername='" + rocketChatUsername + '\'' + - ", rocketChatAuthToken='" + rocketChatAuthToken + '\'' + - ", rocketChatPersonalAccessToken='" + rocketChatPersonalAccessToken + '\'' + - ", rocketChatUserId='" + rocketChatUserId + '\'' + ", isStudent=" + isStudent + '}'; } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/UserDAO.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/UserDAO.java index 5412add8f2a3764501b3f556be0d9a2c1fa215b5..5deba403998a2624d07f08ebdd349f52779cdec3 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/UserDAO.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/UserDAO.java @@ -29,11 +29,10 @@ public class UserDAO { public void persist(User user, UserProfile profile) { connect.connect(); String mysqlRequest = "INSERT INTO users (`name`, `password`, `email`, `isStudent`," + - "`rocketChatUserId`,`rocketChatUsername`,`rocketChatAuthToken`,`rocketChatPersonalAccessToken`) " + - "values (?,?,?,?,?,?,?,?)"; + "`rocketChatUsername`) " + + "values (?,?,?,?,?)"; connect.issueInsertOrDeleteStatement(mysqlRequest, user.getName(), user.getPassword(), user.getEmail(), - user.getStudent(), user.getRocketChatUserId(), user.getRocketChatUsername(), user.getRocketChatAuthToken(), - user.getRocketChatPersonalAccessToken()); + user.getStudent(),user.getRocketChatUsername()); connect.close(); // TODO implmement UserProfile @Mar } @@ -45,14 +44,11 @@ public class UserDAO { connect.close(); } - public void update(User user) { - String mysqlRequest = "UPDATE `users` SET `name`=?,`password`=?,`email`=?,`isStudent`=?," + - "`rocketChatId`=?,`rocketChatAuthToken`=? WHERE email=? LIMIT 1"; - //TODO: maybe add handling if a line is actually updated - //TODO: if user is updated, it also must update all other tables which includes some information about the user, for example project user + public void updateRocketChatUserName(User user) { + String mysqlRequest = "UPDATE `users` SET" + + "`rocketChatUsername`=? WHERE email=? LIMIT 1"; connect.connect(); - connect.issueUpdateStatement(mysqlRequest, user.getName(), user.getPassword(), user.getEmail(), - user.getStudent(), user.getRocketChatUserId(), user.getRocketChatAuthToken(), user.getEmail()); + connect.issueUpdateStatement(mysqlRequest, user.getRocketChatUsername(), user.getEmail()); connect.close(); } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/UserView.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/UserView.java index a06604a7ff2186526240984414b430367b90b5cf..b9b81e674dfb3d0d83d33cdc564008d94d1d2ed5 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/UserView.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/modules/user/UserView.java @@ -1,6 +1,8 @@ package unipotsdam.gf.modules.user; +import unipotsdam.gf.exceptions.*; import unipotsdam.gf.modules.project.Management; +import unipotsdam.gf.process.ProjectCreationProcess; import unipotsdam.gf.session.GFContexts; import unipotsdam.gf.interfaces.ICommunication; @@ -24,11 +26,18 @@ import java.net.URISyntaxException; @ManagedBean public class UserView { + @Inject private ICommunication communicationService; + + @Inject private UserDAO userDAO; + @Inject private Management management; + @Inject + private ProjectCreationProcess projectCreationProcess; + @Inject public UserView(ICommunication communicationService, UserDAO userDAO, Management management) { this.communicationService = communicationService; @@ -55,8 +64,33 @@ public class UserView { @FormParam("email") String email, @FormParam("isStudent") String isStudent) throws URISyntaxException { User user = new User(name, password, email, isStudent == null); - return login(req, true, user); + try { + projectCreationProcess.createUser(user); + } catch (UserExistsInRocketChatException e) { + return registrationError(); + } catch (UserExistsInMysqlException e) { + String existsUrl = "../register.jsp?userExists=true"; + return forwardToLocation(existsUrl); + } catch (RocketChatDownException e) { + e.printStackTrace(); + return registrationError(); + } + try { + projectCreationProcess.authenticateUser(user, req); + } catch (UserDoesNotExistInRocketChatException e) { + loginError(); + } catch (RocketChatDownException e) { + e.printStackTrace(); + return registrationError(); + } + return redirectToProjectPage(user); + + } + + private Response redirectToUserExists() throws URISyntaxException { + String existsUrl = "../index.jsp?userExists=false"; + return forwardToLocation(existsUrl); } /** @@ -72,17 +106,25 @@ public class UserView { @POST @Produces(MediaType.TEXT_HTML) @Path("/exists") - public Response existsUser( + public Response authenticate( @Context HttpServletRequest req, @FormParam("name") String name, @FormParam("password") String password, @FormParam("email") String email) throws URISyntaxException { User user = new User(name, password, email, null); - boolean isLoggedIn = communicationService.loginUser(user); - if (isLoggedIn) { - return login(req, false, user); - } else { + try { + Boolean exists = projectCreationProcess.authenticateUser(user, req); + if (exists) { + user = fillUserFields(user); + return redirectToProjectPage(user); + } else { + return loginError(); + } + } catch (UserDoesNotExistInRocketChatException e) { + return loginError(); + } catch (RocketChatDownException e) { return loginError(); } + } @POST @@ -99,40 +141,6 @@ public class UserView { } - /** - * if create User is true, the user is created and logged in if he does not exist - * - * @param createUser - * @param user - * @return - * @throws URISyntaxException - */ - public Response login(HttpServletRequest req, boolean createUser, User user) throws URISyntaxException { - - if (management.exists(user)) { - if (!createUser) { - user = fillUserFields(user); - return redirectToProjectPage(req, user); - } - String existsUrl = "../register.jsp?userExists=true"; - return forwardToLocation(existsUrl); - } else { - if (createUser) { - boolean isRegisteredAndLoggedIn = communicationService.registerUser(user); - if (!isRegisteredAndLoggedIn) { - return registrationError(); - } - management.create(user, null); - user = fillUserFields(user); - return redirectToProjectPage(req, user); - } else { - String existsUrl = "../index.jsp?userExists=false"; - return forwardToLocation(existsUrl); - } - - } - } - private User fillUserFields(User user) { user = userDAO.getUserByEmail(user.getEmail()); return user; @@ -155,15 +163,14 @@ public class UserView { * @return * @throws URISyntaxException */ - private Response redirectToProjectPage(HttpServletRequest req, User user) throws URISyntaxException { + private Response redirectToProjectPage(User user) throws URISyntaxException { String successUrl; if (user.getStudent() != null && user.getStudent()) { - successUrl = "../project/overview-student.jsp"; + successUrl = "../project/myCourses-student.jsp"; } else { successUrl = "../project/overview-docent.jsp"; } - req.getSession().setAttribute(GFContexts.USEREMAIL, user.getEmail()); Response result = forwardToLocation(successUrl); return result; } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/mysql/MysqlConnectImpl.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/mysql/MysqlConnectImpl.java index d0aecb4b7b4d8026a6b18327406833dc17a1cb8a..789cdb9a2f3766c625c3e9a5a7f2ed15129a4919 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/mysql/MysqlConnectImpl.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/mysql/MysqlConnectImpl.java @@ -137,6 +137,7 @@ public class MysqlConnectImpl implements MysqlConnect { } private void printErrorMessage(String statement, SQLException ex) { + ex.printStackTrace(); String message = ex.toString() + " for statement \n" + statement; log.error(message); System.out.println(message); diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/DossierCreationProcess.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/DossierCreationProcess.java index b4379f195d1ee13ddc37552f659c2ee1586f262d..6b2f1fb40a135f4d5402f49abb5323f51c076aa0 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/DossierCreationProcess.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/DossierCreationProcess.java @@ -1,21 +1,19 @@ package unipotsdam.gf.process; import unipotsdam.gf.interfaces.Feedback; -import unipotsdam.gf.modules.project.Management; import unipotsdam.gf.modules.project.Project; -import unipotsdam.gf.modules.project.ProjectDAO; import unipotsdam.gf.modules.submission.controller.SubmissionController; import unipotsdam.gf.modules.submission.model.FullSubmission; import unipotsdam.gf.modules.submission.model.FullSubmissionPostRequest; import unipotsdam.gf.modules.user.User; import unipotsdam.gf.modules.user.UserDAO; -import unipotsdam.gf.process.constraints.Constraints; import unipotsdam.gf.process.constraints.ConstraintsImpl; import unipotsdam.gf.process.phases.Phase; import unipotsdam.gf.process.tasks.*; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.ArrayList; import java.util.List; @Singleton @@ -29,11 +27,14 @@ public class DossierCreationProcess { private TaskDAO taskDAO; @Inject - private Feedback feedback; + private UserDAO userDAO; @Inject private ConstraintsImpl constraints; + @Inject + private Feedback feedback; + /** * start the Dossier Phase * @@ -82,27 +83,41 @@ public class DossierCreationProcess { submissionController.markAsFinal(fullSubmission); // mark annotate task as finished in db - Task task = new Task(TaskName.ANNOTATE_DOSSIER, user.getEmail(), fullSubmission.getProjectName(), + Task task = new Task(TaskName.ANNOTATE_DOSSIER, user.getEmail(), project.getName(), Progress.FINISHED); taskDAO.updateForUser(task); if (constraints.checkIfFeedbackCanBeDistributed(project)) { - // distributefeedbacks - feedback.assignFeedbackTasks(project); - - // persist tasks for feedback - taskDAO.persistMemberTask( - project, TaskName.GIVE_FEEDBACK, Phase.DossierFeedback); + // create Task to give Feedback + List<User> projectParticipants = userDAO.getUsersByProjectName(project.getName()); + List<Task> allFeedbackTasks = new ArrayList<>(); + for (User participant : projectParticipants) { + Task giveFeedbackTask = taskDAO.createDefault( + project, participant, TaskName.GIVE_FEEDBACK, Phase.DossierFeedback); + taskDAO.persist(giveFeedbackTask); + allFeedbackTasks.add(giveFeedbackTask); + } + //specifies user, who needs to give a feedback in DB + feedback.specifyFeedbackTasks(allFeedbackTasks); } } + public void createCloseFeedBackPhaseTask(Project project) { + taskDAO.persistTeacherTask(project, TaskName.CLOSE_DOSSIER_FEEDBACK_PHASE, Phase.DossierFeedback); + } public void finishPhase(Project project) { - /* - TODO implement - */ - /** TODO: Move this to the dossierCreationProcess - /* if (tasks.size() > 0) { + + User user = userDAO.getUserByEmail(project.getAuthorEmail()); + Task task = new Task(); + task.setUserEmail(user.getEmail()); + task.setProjectName(project.getName()); + task.setProgress(Progress.FINISHED); + task.setTaskName(TaskName.CLOSE_DOSSIER_FEEDBACK_PHASE); + taskDAO.updateForUser(task); + taskDAO.persist(taskDAO.createDefault(project, user, TaskName.WAIT_FOR_REFLECTION, Phase.Execution)); + //todo: implement communication stuff + /* if (tasks.size() > 0) { iCommunication.informAboutMissingTasks(tasks, project); } else { // send a message to the users informing them about the start of the new phase diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/GroupFormationProcess.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/GroupFormationProcess.java index b2cbfb34fc03a378c231746fdd9726c05c9899c8..a289c669c5c442b0dae8ad0c87fae0a588454d9c 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/GroupFormationProcess.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/GroupFormationProcess.java @@ -1,8 +1,12 @@ package unipotsdam.gf.process; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; +import unipotsdam.gf.interfaces.ICommunication; import unipotsdam.gf.interfaces.IGroupFinding; import unipotsdam.gf.interfaces.IPhases; import unipotsdam.gf.modules.group.Group; +import unipotsdam.gf.modules.group.GroupData; import unipotsdam.gf.modules.group.GroupFormationMechanism; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.modules.project.ProjectDAO; @@ -16,6 +20,7 @@ import unipotsdam.gf.process.tasks.TaskName; import javax.inject.Inject; import javax.inject.Singleton; import java.util.Arrays; +import java.util.List; @Singleton public class GroupFormationProcess { @@ -26,8 +31,6 @@ public class GroupFormationProcess { @Inject TaskDAO taskDAO; - - @Inject private DossierCreationProcess dossierCreationProcess; @@ -38,9 +41,7 @@ public class GroupFormationProcess { projectDAO.setGroupFormationMechanism(groupFormationMechanism, project); } - // taskDAO.persistTeacherTask(project, TaskName.FORM_GROUPS_MANUALLY, Phase.GroupFormation); - /** * this method can only be called to change the group formation to manual groups or single * @param groupFormationMechanism @@ -55,9 +56,8 @@ public class GroupFormationProcess { * and if there groups are not handled manually * this method finalizes the groups * @param project - * @param groups */ - public void finish(Project project, Group ... groups) { + public void finalize(Project project) throws RocketChatDownException, UserDoesNotExistInRocketChatException { taskDAO.persistTeacherTask(project, TaskName.CLOSE_GROUP_FINDING_PHASE, Phase.GroupFormation); /** * Gruppenphase wird beendet @@ -71,7 +71,22 @@ public class GroupFormationProcess { // Die Studierenden müssen nicht mehr auf die Gruppenfindung warten taskDAO.finishMemberTask(project, TaskName.WAITING_FOR_GROUP); taskDAO.persistMemberTask(project, TaskName.CONTACT_GROUP_MEMBERS, Phase.GroupFormation); + + //if the project is finalized create group chat room + groupfinding.finalizeGroups(project); } + public GroupData getOrInitializeGroups(Project project) { + List<Group> groups = groupfinding.getGroups(project); + if (groups.isEmpty()) { + groups = groupfinding.createRandomGroups(project); + } + return new GroupData(groups); + } + + public void saveGroups(List<Group> groups,Project project){ + groupfinding.deleteGroups(project); + groupfinding.persistGroups(groups, project); + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/ProjectCreationProcess.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/ProjectCreationProcess.java index 6549b063b6ba49b1cb2b4d450e06f0894111e243..35078a93ff5808b079a10305ca711961c1b67507 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/ProjectCreationProcess.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/ProjectCreationProcess.java @@ -1,22 +1,30 @@ package unipotsdam.gf.process; -import unipotsdam.gf.interfaces.IPhases; +import unipotsdam.gf.exceptions.*; +import unipotsdam.gf.interfaces.ICommunication; +import unipotsdam.gf.modules.annotation.model.Category; +import unipotsdam.gf.modules.assessment.AssessmentMechanism; +import unipotsdam.gf.modules.communication.model.RocketChatUser; import unipotsdam.gf.modules.group.GroupDAO; import unipotsdam.gf.modules.group.GroupFormationMechanism; import unipotsdam.gf.modules.project.Management; import unipotsdam.gf.modules.project.Project; +import unipotsdam.gf.modules.project.ProjectConfiguration; import unipotsdam.gf.modules.user.User; import unipotsdam.gf.process.constraints.ConstraintsImpl; import unipotsdam.gf.process.phases.Phase; -import unipotsdam.gf.process.tasks.Progress; import unipotsdam.gf.process.tasks.Task; import unipotsdam.gf.process.tasks.TaskDAO; import unipotsdam.gf.process.tasks.TaskName; +import unipotsdam.gf.session.GFContexts; import javax.inject.Inject; import javax.inject.Singleton; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.WebApplicationException; -import java.io.IOException; +import java.util.HashMap; + +import static unipotsdam.gf.modules.group.GroupFormationMechanism.SingleUser; @Singleton @@ -35,15 +43,20 @@ public class ProjectCreationProcess { @Inject private GroupDAO groupDAO; + @Inject + private ICommunication iCommunication; + + @Inject + private GFContexts gfContexts; /** * STEP 1 * - * @param project - * @param author - * @throws IOException + * @param project which project is created + * @param author who creates the project */ - public void createProject(Project project, User author) throws IOException { + public void createProject(Project project, User author) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { project.setAuthorEmail(author.getEmail()); try { iManagement.create(project); @@ -51,15 +64,20 @@ public class ProjectCreationProcess { throw new WebApplicationException("Project already exists"); } taskDao.createTaskWaitForParticipants(project, author); + + // create chatromm + iCommunication.createEmptyChatRoom(project.getName(), false); + } /** * STEP 2 * - * @param project - * @param user + * @param project which project is entered + * @param user who is participates the project */ - public void studentEntersProject(Project project, User user) { + public void studentEntersProject(Project project, User user) + throws RocketChatDownException, UserDoesNotExistInRocketChatException { // student enters project iManagement.register(user, project, null); @@ -72,7 +90,7 @@ public class ProjectCreationProcess { Boolean groupsCanBeFormed = constraintsImpl.checkIfGroupsCanBeFormed(project); if (groupsCanBeFormed) { GroupFormationMechanism groupFormationMechanism = groupDAO.getGroupFormationMechanism(project); - if (!groupFormationMechanism.equals(GroupFormationMechanism.SingleUser) && !groupFormationMechanism + if (!groupFormationMechanism.equals(SingleUser) && !groupFormationMechanism .equals(GroupFormationMechanism.Manual)) { taskDao.persistTeacherTask(project, TaskName.EDIT_FORMED_GROUPS, Phase.GroupFormation); } else { @@ -81,5 +99,53 @@ public class ProjectCreationProcess { //phases.endPhase(Phase.GroupFormation, project); } } + iCommunication.addUserToChatRoom(user, project.getName()); } + + public void createUser(User user) + throws UserExistsInMysqlException, RocketChatDownException, UserExistsInRocketChatException { + if(iManagement.exists(user)) { + throw new UserExistsInMysqlException(); + } + // create user in rocket chat + iCommunication.registerUser(user); + // create user in mysql + iManagement.create(user, null); + + } + + public Boolean authenticateUser(User user, HttpServletRequest req) + throws UserDoesNotExistInRocketChatException, RocketChatDownException { + // todo implement + + RocketChatUser isLoggedIn = iCommunication.loginUser(user); + gfContexts.updateUserSessionWithRocketChat(req, isLoggedIn); + gfContexts.updateUserWithEmail(req, isLoggedIn); + return iManagement.exists(user); + } + + public void deleteUser(User user) throws RocketChatDownException, UserDoesNotExistInRocketChatException { + iManagement.delete(user); + iCommunication.delete(user); + } + + public void deleteProject(Project project) throws RocketChatDownException, UserDoesNotExistInRocketChatException { + // TODO implement + iManagement.delete(project); + iCommunication.deleteChatRoom(project); + } + + /* *//** + * STEP N + * + * @param project the project to delete + *//* + public void deleteProject(Project project) { + try { + iManagement.delete(project); + } catch (Exception e) { + throw new WebApplicationException("Project already exists"); + } + //taskDao.createTaskWaitForParticipants(project, author); + }*/ } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/constraints/ConstraintsImpl.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/constraints/ConstraintsImpl.java index 7948515f03de3c89904bbcfe0739146b6bde5e24..27da77e6899d7b1799c0984d147de43bdd772f5e 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/constraints/ConstraintsImpl.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/constraints/ConstraintsImpl.java @@ -73,7 +73,16 @@ public class ConstraintsImpl { return result; } - - + public List<String> checkWhichFeedbacksAreMissing(Project project){ + List<User> participants = userDAO.getUsersByProjectName(project.getName()); + List<User> doneParticipants = submissionController.getAllUsersWithFinalizedFeedback(project); + List<String> missingFeedbacksFrom = new ArrayList<>(); + for(User participant: participants){ + if (!doneParticipants.contains(participant)){ + missingFeedbacksFrom.add(participant.getName()); + } + } + return missingFeedbacksFrom; + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/phases/PhaseView.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/phases/PhaseView.java index a021beb02748ed9d951e0f25436a690ae9082087..1abad11c81b8e4aa972a2d89699b6bf9f35a0c0d 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/phases/PhaseView.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/phases/PhaseView.java @@ -1,5 +1,7 @@ package unipotsdam.gf.process.phases; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.modules.project.ProjectDAO; import unipotsdam.gf.interfaces.IPhases; @@ -41,7 +43,7 @@ public class PhaseView { @Path("/{projectPhase}/projects/{projectName}/end") @GET public String endPhase(@PathParam("projectPhase") String projectPhase, @PathParam("projectName") String - projectName) throws URISyntaxException { + projectName) throws URISyntaxException, RocketChatDownException, UserDoesNotExistInRocketChatException { Phase phase = Phase.valueOf(projectPhase); Project project = projectDAO.getProjectByName(projectName); phases.endPhase(phase, project); diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/phases/PhasesImpl.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/phases/PhasesImpl.java index 973fbee8f0fda35105cbe99c6604b6379248e6fd..834a917a74d5415e412eddd99f629dec55589f54 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/phases/PhasesImpl.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/phases/PhasesImpl.java @@ -1,5 +1,7 @@ package unipotsdam.gf.process.phases; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.interfaces.ICommunication; import unipotsdam.gf.interfaces.IJournal; @@ -71,7 +73,7 @@ public class PhasesImpl implements IPhases { */ @Override - public void endPhase(Phase currentPhase, Project project) { + public void endPhase(Phase currentPhase, Project project) throws RocketChatDownException, UserDoesNotExistInRocketChatException { Phase changeToPhase = getNextPhase(currentPhase); Map<StudentIdentifier, ConstraintsMessages> tasks; switch (currentPhase) { @@ -79,7 +81,7 @@ public class PhasesImpl implements IPhases { // inform users about the formed groups, optionally giving them a hint on what happens next iCommunication.sendMessageToUsers(project, Messages.GroupFormation(project)); saveState(project, changeToPhase); - groupFormationProcess.finish(project); + groupFormationProcess.finalize(project); dossierCreationProcess.start(project); break; case DossierFeedback: diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/FeedbackTaskData.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/FeedbackTaskData.java index 0f015ded76b117beba05133234ff0c038f183eb3..81ce2238a3a3d74a4a33840736f546108810934d 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/FeedbackTaskData.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/FeedbackTaskData.java @@ -1,6 +1,6 @@ package unipotsdam.gf.process.tasks; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import unipotsdam.gf.modules.submission.model.FullSubmission; import unipotsdam.gf.modules.user.User; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskDAO.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskDAO.java index f2a2232824f9c0620d136794e11fee138dc2e6b3..992d97837f02e7f1b7334c699820a4ff88c73233 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskDAO.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskDAO.java @@ -1,21 +1,28 @@ package unipotsdam.gf.process.tasks; import unipotsdam.gf.interfaces.IGroupFinding; +import unipotsdam.gf.modules.group.GroupFormationMechanism; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.modules.project.ProjectDAO; import unipotsdam.gf.modules.submission.controller.SubmissionController; -import unipotsdam.gf.modules.submission.view.SubmissionRenderData; -import unipotsdam.gf.modules.user.UserDAO; -import unipotsdam.gf.process.phases.Phase; import unipotsdam.gf.modules.user.User; +import unipotsdam.gf.modules.user.UserDAO; import unipotsdam.gf.mysql.MysqlConnect; import unipotsdam.gf.mysql.VereinfachtesResultSet; +import unipotsdam.gf.process.constraints.ConstraintsImpl; +import unipotsdam.gf.process.phases.Phase; import javax.annotation.ManagedBean; import javax.inject.Inject; +import java.sql.Timestamp; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Comparator; +import java.util.List; -import static unipotsdam.gf.process.tasks.TaskName.*; +import static unipotsdam.gf.process.tasks.TaskName.WAITING_FOR_GROUP; +import static unipotsdam.gf.process.tasks.TaskName.WAIT_FOR_PARTICPANTS; @ManagedBean public class TaskDAO { @@ -36,6 +43,9 @@ public class TaskDAO { @Inject private SubmissionController submissionController; + @Inject + private ConstraintsImpl constraints; + // fill the task with the general data private Task getGeneralTask(VereinfachtesResultSet vereinfachtesResultSet) { Task task = new Task(); @@ -88,15 +98,23 @@ public class TaskDAO { project.setName(vereinfachtesResultSet.getString("projectName")); ParticipantsCount participantsCount = projectDAO.getParticipantCount(project); participantsCount.setParticipantsNeeded(groupFinding.getMinNumberOfStudentsNeeded(project)); - task.setTaskData(participantsCount); + Map<String, Object> taskData = new HashMap<>(); + taskData.put("participantCount", participantsCount); + GroupFormationMechanism gfm =groupFinding.getGFM(project); + taskData.put("gfm", gfm); + task.setTaskData(taskData); + if (gfm.equals(GroupFormationMechanism.Manual)){ + task.setTaskType(TaskType.LINKED); + } task.setHasRenderModel(true); return task; } - private Task createDefault(Project project, User target, TaskName taskName, Phase phase) { + public Task createDefault(Project project, User target, TaskName taskName, Phase phase) { Task task = new Task(); task.setTaskName(taskName); task.setEventCreated(System.currentTimeMillis()); + task.setDeadline(System.currentTimeMillis()+3000*60*60*24); task.setProjectName(project.getName()); task.setUserEmail(target.getEmail()); task.setImportance(Importance.MEDIUM); @@ -109,7 +127,7 @@ public class TaskDAO { return task; } - private void persist(Task task) { + public void persist(Task task) { if (task.getTaskName() == null) { throw new Error("no taskName given"); @@ -128,7 +146,10 @@ public class TaskDAO { connect.connect(); String query = - "INSERT IGNORE INTO fltrail.tasks (userEmail, projectName, taskName, " + "groupTask, importance, progress, phase, created, due, " + "taskMode, taskMode2, taskMode3)" + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + "INSERT IGNORE INTO fltrail.tasks (userEmail, projectName, taskName, " + + "groupTask, importance, progress, phase, created, due, " + + "taskMode, taskMode2, taskMode3)" + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; if (task.getTaskType() == null || task.getTaskType().length == 0) { try { @@ -137,17 +158,18 @@ public class TaskDAO { e.printStackTrace(); } } - + Timestamp creationTime = new Timestamp(task.getEventCreated()); + Timestamp deadline = new Timestamp(task.getDeadline()); connect.issueInsertOrDeleteStatement(query, task.getUserEmail(), task.getProjectName(), task.getTaskName(), - task.getGroupTask(), task.getImportance(), task.getProgress(), task.getPhase(), null, - task.getDeadline(), task.getTaskType()[0].toString(), taskMode2, taskMode3); + task.getGroupTask(), task.getImportance(), task.getProgress(), task.getPhase(), creationTime, + deadline, task.getTaskType()[0].toString(), taskMode2, taskMode3); connect.close(); } // get all the tasks a user has in a specific project public ArrayList<Task> getTasks(User user, Project project) { connect.connect(); - String query = "Select * from tasks where userEmail = ? AND projectName = ?"; + String query = "Select * from tasks where userEmail = ? AND projectName = ? ORDER BY created DESC"; ArrayList<Task> result = new ArrayList<>(); VereinfachtesResultSet vereinfachtesResultSet = connect.issueSelectStatement(query, user.getEmail(), project.getName()); @@ -175,6 +197,7 @@ public class TaskDAO { case GIVE_FEEDBACK: { Task feedbackTask = getGeneralTask(vereinfachtesResultSet); + //feedback.assigningMissingFeedbackTasks(); feedbackTask.setTaskData(submissionController.getFeedbackTaskData(user, project)); feedbackTask.setHasRenderModel(true); result.add(feedbackTask); @@ -184,6 +207,21 @@ public class TaskDAO { Task task = getGeneralTask(vereinfachtesResultSet); task.setHasRenderModel(true); task.setTaskData(submissionController.getProgressData(project)); + result.add(task); + break; + } + case CLOSE_DOSSIER_FEEDBACK_PHASE: { + Task task = getGeneralTask(vereinfachtesResultSet); + task.setHasRenderModel(true); + List<String> missingFeedbacks = constraints.checkWhichFeedbacksAreMissing(project); + task.setTaskData(missingFeedbacks); //frontendCheck if missingFeedbacks.size ==0 + result.add(task); + Task waitingForDossiers = new Task(); + waitingForDossiers.setUserEmail(user.getEmail()); + waitingForDossiers.setProjectName(project.getName()); + waitingForDossiers.setProgress(Progress.FINISHED); + waitingForDossiers.setTaskName(TaskName.WAITING_FOR_STUDENT_DOSSIERS); + updateForUser(waitingForDossiers); break; } default: { @@ -191,9 +229,9 @@ public class TaskDAO { } } } - connect.close(); - + TaskOrder taskOrder = new TaskOrder(); + result.sort(taskOrder.byName); return result; } @@ -275,7 +313,7 @@ public class TaskDAO { } /* - * if this takes long rewrite it as batch update + * if this takes long rewrite it as batch updateRocketChatUserName */ public void finishMemberTask(Project project, TaskName taskName) { java.util.List<User> members = userDAO.getUsersByProjectName(project.getName()); @@ -284,4 +322,6 @@ public class TaskDAO { updateForUser(task); } } + + } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskName.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskName.java index 75388d34b1f6a5b109f0e1f3ccdc8a1ef0ffcba5..b6aae298ce2db3c1ee161d8ab8c34f88d6f5a257 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskName.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskName.java @@ -3,9 +3,10 @@ package unipotsdam.gf.process.tasks; public enum TaskName { // Teacher Tasks WAIT_FOR_PARTICPANTS, CLOSE_GROUP_FINDING_PHASE, WAITING_FOR_GROUP, + CLOSE_DOSSIER_FEEDBACK_PHASE,WAIT_FOR_REFLECTION, FORM_GROUPS_MANUALLY, // Student Tasks UPLOAD_DOSSIER, GIVE_FEEDBACK, CREATE_QUIZ, WRITE_EJOURNAL, ANNOTATE_DOSSIER, FINALIZE_DOSSIER, FINALIZE_EJOURNAL, - FORM_GROUPS_MANUALLY, EDIT_FORMED_GROUPS, CONTACT_GROUP_MEMBERS, ASSESSMENT, WAITING_FOR_STUDENT_DOSSIERS, EDIT_FEEDBACK + EDIT_FORMED_GROUPS, CONTACT_GROUP_MEMBERS, ASSESSMENT, WAITING_FOR_STUDENT_DOSSIERS, EDIT_FEEDBACK } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskOrder.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..058b1cc0da7a118cf98054a3f579f1c0e7e67101 --- /dev/null +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskOrder.java @@ -0,0 +1,39 @@ +package unipotsdam.gf.process.tasks; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +public class TaskOrder { + private List<TaskName> orderedTasks; + + public TaskOrder(){ + List<TaskName> result = new ArrayList<>(); + result.add(TaskName.WAIT_FOR_PARTICPANTS); + result.add(TaskName.WAITING_FOR_GROUP); + result.add(TaskName.CLOSE_GROUP_FINDING_PHASE); + result.add(TaskName.CONTACT_GROUP_MEMBERS); + result.add(TaskName.WAITING_FOR_STUDENT_DOSSIERS); + result.add(TaskName.UPLOAD_DOSSIER); + result.add(TaskName.ANNOTATE_DOSSIER); + result.add(TaskName.GIVE_FEEDBACK); + result.add(TaskName.CLOSE_DOSSIER_FEEDBACK_PHASE); + this.orderedTasks = result; + } + public TaskName getNextTask(TaskName taskName){ + return orderedTasks.get(orderedTasks.indexOf(taskName)+1); + } + + public TaskName getPreviousTask(TaskName taskName){ + return orderedTasks.get(orderedTasks.indexOf(taskName)-1); + } + + public Comparator<Task> byName = (o1, o2) -> { + if (o1.getTaskName().equals(o2.getTaskName())) { + return 0; + } else { + return orderedTasks.indexOf(o1.getTaskName()) > orderedTasks.indexOf(o2.getTaskName()) ? -1 : 1; + } + }; +} diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskView.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskView.java index e32e5a9913743d96f08e8384f08e339881bad8e8..a55bb2ff6229ebdcf0f280b0a625025c1f263d26 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskView.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/process/tasks/TaskView.java @@ -1,6 +1,7 @@ package unipotsdam.gf.process.tasks; import unipotsdam.gf.modules.project.Project; +import unipotsdam.gf.modules.project.ProjectDAO; import unipotsdam.gf.modules.user.User; import javax.inject.Inject; @@ -15,6 +16,8 @@ import java.util.ArrayList; @Path("/tasks") public class TaskView { + @Inject + private ProjectDAO projectDAO; @Inject private TaskDAO taskDAO; diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/session/GFContexts.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/session/GFContexts.java index 1c4f46a42c6c2539c67e2779f702c079625acb3f..7ec5417a9279235333ccc1b9cf98815e29ba111b 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/session/GFContexts.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/session/GFContexts.java @@ -1,5 +1,6 @@ package unipotsdam.gf.session; +import unipotsdam.gf.modules.communication.model.RocketChatUser; import unipotsdam.gf.modules.user.User; import unipotsdam.gf.modules.user.UserDAO; @@ -14,6 +15,10 @@ public class GFContexts { public static final String USEREMAIL = "userEmail"; public static final String PROJECTNAME = "projectName"; + public static final String ROCKETCHATAUTHTOKEN = "rocketchatauthtoken"; + public static final String ROCKETCHATID = "rocketchatid"; + + public String getUserEmail(HttpServletRequest req) throws IOException { Object userEmail = req.getSession().getAttribute(GFContexts.USEREMAIL); if (userEmail == null) { @@ -27,4 +32,13 @@ public class GFContexts { String userEmail = getUserEmail(req); return userDAO.getUserByEmail(userEmail); } + + public void updateUserSessionWithRocketChat(HttpServletRequest req, RocketChatUser user) { + req.getSession().setAttribute(GFContexts.ROCKETCHATAUTHTOKEN, user.getRocketChatAuthToken()); + req.getSession().setAttribute(GFContexts.ROCKETCHATID, user.getRocketChatUserId()); + } + + public void updateUserWithEmail(HttpServletRequest req, User user) { + req.getSession().setAttribute(GFContexts.USEREMAIL, user.getEmail()); + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/session/SessionExistsFilter.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/session/SessionExistsFilter.java index 32e370b17d1770a6e5f0b5075272c9fe3fe615a9..16c4db2eb668a1e81ce4746410953b340a497df6 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/session/SessionExistsFilter.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/session/SessionExistsFilter.java @@ -52,8 +52,8 @@ public class SessionExistsFilter implements Filter { Object attribute = request1.getSession().getAttribute(GFContexts.USEREMAIL); if (attribute == null) { - //redirectToLogin(request, response); - request1.getSession().setAttribute(GFContexts.USEREMAIL, "vodkas@yolo.com"); + redirectToLogin(request, response); + //request1.getSession().setAttribute(GFContexts.USEREMAIL, "vodkas@yolo.com"); chain.doFilter(request,response); } else { final ServiceLocator locator = ServiceLocatorUtilities.bind(new GFApplicationBinder()); diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/ChatWindow.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/ChatWindow.java index e54af9c58caea0bc7c9eefd709954b48da720b93..09f624f0e549884b855ca921748647de9050223b 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/ChatWindow.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/ChatWindow.java @@ -5,10 +5,15 @@ import org.glassfish.hk2.utilities.ServiceLocatorUtilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import unipotsdam.gf.config.GFApplicationBinder; +import unipotsdam.gf.config.GFRocketChatConfig; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; import unipotsdam.gf.interfaces.ICommunication; import unipotsdam.gf.modules.communication.service.CommunicationService; +import unipotsdam.gf.session.GFContext; import unipotsdam.gf.session.GFContexts; +import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; @@ -21,6 +26,11 @@ public class ChatWindow extends SimpleTagSupport { private String orientation; + private String scope; + + @Inject + private ICommunication communicationService; + public void doTag() throws IOException { final ServiceLocator locator = ServiceLocatorUtilities.bind(new GFApplicationBinder()); @@ -29,14 +39,46 @@ public class ChatWindow extends SimpleTagSupport { PageContext pageContext = (PageContext) getJspContext(); HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); /*String token = request.getParameter("token"); */ - String projectId = request.getParameter("projectName"); + String projectName = request.getParameter("projectName"); + String userEmail = request.getSession().getAttribute(GFContexts.USEREMAIL).toString(); + + /** + * create project chatroom + */ + if (getScope() == "project") { + String chatRoomLink = communicationService.getProjectChatRoomLink(projectName); + writeIframe(request, chatRoomLink); + /** + * create group chatroom + */ + } else { + // scope is group + String projectChatRoomLink = null; + try { + projectChatRoomLink = communicationService.getChatRoomLink(userEmail, projectName); + } catch (RocketChatDownException e) { + e.printStackTrace(); + } catch (UserDoesNotExistInRocketChatException e) { + e.printStackTrace(); + } + if (projectChatRoomLink != null) { + writeIframe(request, projectChatRoomLink); + } + } - ICommunication communicationService = new CommunicationService(); - String chatRoomLink = communicationService.getChatRoomLink(request.getSession().getAttribute(GFContexts - .USEREMAIL).toString(), projectId); - log.debug("ChatRoomLink for ChatWindow: {}", chatRoomLink); + } + + private void writeIframe(HttpServletRequest request, String chatRoomLink) throws + IOException { + String getAuthToken = request.getSession().getAttribute(GFContexts.ROCKETCHATAUTHTOKEN).toString(); + String getId = request.getSession().getAttribute(GFContexts.ROCKETCHATID).toString(); JspWriter out = getJspContext().getOut(); - out.println("<iframe height=\"400px\" src=\"" + chatRoomLink + "\"/>"); + + log.debug("chatroom links for ChatWindow: {}", chatRoomLink); + + out.println("<iframe height=\"400px\" src=\"" + chatRoomLink + "\">"); + out.println("</iframe>"); + } public void setOrientation(String orientation) { @@ -46,4 +88,12 @@ public class ChatWindow extends SimpleTagSupport { public String getOrientation() { return orientation; } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getScope() { + return scope; + } } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/Footer.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/Footer.java index 882f0e958f97ddc8bf40a457297e2a84aa45b7c3..bbdc1e7f7518620b0d80d66d1d61fd30933dfb7e 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/Footer.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/Footer.java @@ -8,16 +8,16 @@ public class Footer extends SimpleTagSupport { public void doTag() throws IOException { JspWriter out = getJspContext().getOut(); - String footerString = " </div>\n" + + String footerString = "</div> </div>\n" + "\n" + " <div style=\"clear:left\"></div>\n" + "\n" + "</main>\n" + "<footer>\n" + " <p>\n" + - " Impressum </br>\n" + - " Ansprechpartner</br>\n" + - " Fides</br>\n" + + " <a href=\"#\"> Impressum </a> </br>\n" + + " <a href=\"#\"> Ansprechpartner</a></br>\n" + + " <a href=\"#\"> Fides</a></br>\n" + " </p>\n" + "</footer>\n"; footerString += "" + diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/HeadLine.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/HeadLine.java index 7365b48076c44c75cc15f21da6a7f9ea1dfe600b..b4adaa71a57d58a944af86e93faa7f4609f91276 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/HeadLine.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/HeadLine.java @@ -49,7 +49,6 @@ public class HeadLine extends SimpleTagSupport { } else { out.println("Dozentenübersicht " + user.getName()); } - } out.println("</h1>\n"); } diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/Menu.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/Menu.java index 9de604d0944b026d21e071a682a164ca028e2079..1f9b4b9d34ed68187972e0f3b51931c362002f6d 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/Menu.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/taglibs/Menu.java @@ -5,7 +5,6 @@ import org.glassfish.hk2.utilities.ServiceLocatorUtilities; import unipotsdam.gf.config.GFApplicationBinder; import unipotsdam.gf.modules.project.ProjectDAO; import unipotsdam.gf.process.phases.Phase; -import unipotsdam.gf.process.tasks.TaskDAO; import unipotsdam.gf.modules.user.User; import unipotsdam.gf.modules.user.UserDAO; import unipotsdam.gf.session.GFContexts; @@ -20,10 +19,6 @@ import java.io.IOException; public class Menu extends SimpleTagSupport { - - @Inject - private TaskDAO taskDAO; - @Inject private UserDAO userDAO; @@ -57,9 +52,14 @@ public class Menu extends SimpleTagSupport { String menuString = "" + " <header>\n" + " <div class=\"row\">\n" + - " <div class=\"nav-group-left\">" + - " <a class=\"nav-link\" href=\"" + OmniDependencies.hierarchyToString(hierarchyLevel) + "project/overview-student.jsp?projectName=" + projectName + "\">meine Projekte</a>\n" + - " <a class=\"nav-link\" href=\"" + OmniDependencies.hierarchyToString(hierarchyLevel) + "profile/profile.jsp?projectName=" + projectName + "\">Profil</a>\n"+ + " <div class=\"nav-group-left\">"; + if (isStudent){ + menuString+=" <a class=\"nav-link\" style=\"color:white;\" href=\"" + OmniDependencies.hierarchyToString(hierarchyLevel) + "project/myCourses-student.jsp\">Home</a>\n"+ + " <a class=\"nav-link\" style=\"color:white;\" href=\"" + OmniDependencies.hierarchyToString(hierarchyLevel) + "project/search-project.jsp\">suche Kurs</a>\n"; + }else{ + menuString+=" <a class=\"nav-link\" style=\"color:white;\" href=\"" + OmniDependencies.hierarchyToString(hierarchyLevel) + "project/overview-docent.jsp\">meine Projekte</a>\n"; + } + menuString+=" <a class=\"nav-link\" href=\"" + OmniDependencies.hierarchyToString(hierarchyLevel) + "profile/profile.jsp?projectName=" + projectName + "\">Profil</a>\n"+ " </div>" + " <div class=\"nav-group-right\">" + " <a class=\"nav-link\" id=\"logout\" style=\"cursor:pointer\">Logout</a>\n" + @@ -82,12 +82,17 @@ public class Menu extends SimpleTagSupport { String phaseViewString = "" + "<main>\n" + " <div class=\"row group\">\n" + - " <div class=\"titlerow\">\n" + - "\n" + - " </div>\n" + + " <div class=\"titlerow\">\n <h1 id=\"projectHeadline\">"; + if (projectName != null){ + phaseViewString += "\n" +projectName; + }else{ + phaseViewString += "\n Ãœbersicht für "+userEmail; + } + phaseViewString += "" + + " </h1> </div>\n" + " </div>\n" + "\n" + - " <div class=\"row group nav\">\n" + + " <div class=\"row nav\">\n" + " <a href=\"\" >[<i class=\"fas fa-chevron-left\"> zurueck ]</i></a>\n" + " </div>\n" + "\n" + @@ -144,7 +149,8 @@ public class Menu extends SimpleTagSupport { } phaseViewString += "" + " </ul>\n" + - " </div>"; + " </div>" + + "<div class=\"col span_l_of_2\" style=\"\"> <!-- col right-->\n"; out.println(phaseViewString); if (projectName != null) diff --git a/gemeinsamforschen/src/main/java/unipotsdam/gf/util/ResultSetUtil.java b/gemeinsamforschen/src/main/java/unipotsdam/gf/util/ResultSetUtil.java index aeee3a2a09e2e9ba748cd12a3022e9bb6dac400d..ec7d9e929cae335f0f00040014a2897f7bd27ed2 100644 --- a/gemeinsamforschen/src/main/java/unipotsdam/gf/util/ResultSetUtil.java +++ b/gemeinsamforschen/src/main/java/unipotsdam/gf/util/ResultSetUtil.java @@ -9,12 +9,8 @@ public class ResultSetUtil { String name = vereinfachtesResultSet.getString("name"); String password = vereinfachtesResultSet.getString("password"); String email = vereinfachtesResultSet.getString("email"); - String rocketChatId = vereinfachtesResultSet.getString("rocketChatUserId"); - String rocketChatAuthToken = vereinfachtesResultSet.getString("rocketChatAuthToken"); - String rocketChatPersonalAccessToken = vereinfachtesResultSet.getString("rocketChatPersonalAccessToken"); - String rocketChatUserId = vereinfachtesResultSet.getString("rocketChatUserId"); + String rocketChatUserName = vereinfachtesResultSet.getString("rocketChatUserName"); Boolean isStudent = vereinfachtesResultSet.getBoolean("isStudent"); - return new User(name, password, email, rocketChatId, rocketChatAuthToken, - rocketChatPersonalAccessToken, rocketChatUserId, isStudent); + return new User(name, password, email, rocketChatUserName, isStudent); } } diff --git a/gemeinsamforschen/src/main/webapp/annotation/annotation-document.jsp b/gemeinsamforschen/src/main/webapp/annotation/annotation-document.jsp index e71ab6c028bf3f26e728971395ab0716866dd7b2..2912d42e0e0e5aae680bc5f23238230ee4d6b895 100644 --- a/gemeinsamforschen/src/main/webapp/annotation/annotation-document.jsp +++ b/gemeinsamforschen/src/main/webapp/annotation/annotation-document.jsp @@ -1,7 +1,8 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> +<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> <!DOCTYPE html> <html> @@ -10,16 +11,7 @@ <head> <%--<omniDependencies:omniDependencies hierarchy="1"/>--%> - <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> - <link rel="stylesheet" href="../libs/css/styles.css"> - <link rel="stylesheet" href="../taglibs/css/footer.css"> - <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> - <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> - <script src="../taglibs/js/utility.js"></script> - <script src="../taglibs/js/footer.js"></script> - <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" - integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous"> - + <omniDependencies:omniDependencies hierarchy="1"/> <!-- css - annotationStyle --> <link rel="stylesheet" type="text/css" href="css/annotationStyle.css"> <!-- css - contextMenu --> @@ -27,19 +19,18 @@ type="text/css"/> <!-- js - jQuery validation plugin --> - <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.14.0/jquery.validate.min.js"></script> + <script src="../libs/jquery/jqueryValidate.js"></script> <!-- js - jQuery ui position --> - <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js" - type="text/javascript"></script> + <script src="../libs/jquery/jqueryUI.js" type="text/javascript"></script> <!-- js - contextMenu script --> - <script src="https://swisnl.github.io/jQuery-contextMenu/dist/jquery.contextMenu.js" + <script src="../libs/jquery/jqueryContextMenu.js" type="text/javascript"></script> <!-- js - scrollTo --> - <script src="//cdn.jsdelivr.net/npm/jquery.scrollto@2.1.2/jquery.scrollTo.min.js"></script> + <script src="../libs/jquery/jqueryScrollTo.js"></script> <!-- js - rangy Core --> - <script src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-core.js" type="text/javascript"></script> + <script src="../libs/jquery/jqueryRangy.js" type="text/javascript"></script> <!-- js - rangy TextRange Module --> - <script src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-textrange.js" type="text/javascript"></script> + <script src="../libs/jquery/jqueryRangyTextRange.js" type="text/javascript"></script> <!-- js - annotation websocket script --> <script src="js/annotationWebsocket.js"></script> <!-- js - annotation REST script --> @@ -53,106 +44,108 @@ <body> <menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper full-height"> - <div class="container-fluid full-height"> - <div class="container-fluid-content"> - <div class="flex"> - <headLine:headLine/> - </div> - <div class="content-mainpage"> - <div class="leftcolumn"> - <div class="leftcontent"> - <div class="leftcontent-text context-menu-one" id="documentText"></div> - <div class="leftcontent-buttons"> - <div class="leftcontent-buttons-back"> - <button id="btnBack" type="button" class="btn btn-secondary">Zurück</button> - </div> - <div class="leftcontent-buttons-next"> - <button id="btnContinue" type="button" class="btn btn-primary">Weiter</button> - </div> +<div class="page-content-wrapper full-height"> + <div class="container-fluid full-height"> + <div class="container-fluid-content"> + <div class="flex"> + </div> + <div class="content-mainpage"> + <div class="leftcolumn"> + <div class="leftcontent"> + <div class="leftcontent-text context-menu-one" id="documentText"></div> + <div class="leftcontent-buttons"> + <div class="leftcontent-buttons-back"> + <button id="btnBack" type="button" class="btn btn-secondary">Zurück</button> + </div> + <div class="leftcontent-buttons-next"> + <button id="btnWholeCategory" type="button" class="btn btn-primary">kompletten Teil kommentieren</button> + <button id="btnContinue" type="button" class="btn btn-primary">Weiter</button> + <button id="finalize" type="button" class="btn btn-success">finalisieren</button> </div> </div> </div> - <div class="rightcolumn"> - <input type="text" id="annotation-search" onkeyup="searchAnnotation()" - placeholder="Suchen..."> - <div class="rightcontent"> - <ol id="annotations"> - </ol> - </div> + </div> + <div class="rightcolumn"> + <input type="text" id="annotation-search" onkeyup="searchAnnotation()" + placeholder="Suchen..."> + <div class="rightcontent"> + <ol id="annotations"> + </ol> </div> </div> </div> </div> </div> - <!-- annotation create modal --> - <div id="annotation-create-modal" class="modal fade" role="dialog"> - <div class="modal-dialog modal-dialog-centered modal-sm"> - <div class="modal-content"> +</div> +<!-- annotation create modal --> +<div id="annotation-create-modal" class="modal" role="dialog"> + <div class="modal-dialog modal-dialog-centered modal-sm"> + <div class="modal-content"> - <!-- modal header --> - <div class="modal-header flex"> - <h4 class="modal-title flex-one">Annotation</h4> - <button type="button" class="close" data-dismiss="modal">×</button> - </div> + <!-- modal header --> + <div class="modal-header flex"> + <h4 class="modal-title flex-one">Annotation</h4> + <button type="button" class="close" data-dismiss="modal">×</button> + </div> - <!-- modal body --> - <div class="modal-body"> - <form id="annotation-create-form"> - <div class="form-group"> - <label for="annotation-form-title" class="col-form-label">Titel:</label> - <input type="text" class="form-control" id="annotation-form-title" name="title"> - </div> - <div class="form-group"> - <label for="annotation-form-comment" class="col-form-label">Kommentar:</label> - <textarea class="form-control resize-vertical" id="annotation-form-comment" - name="comment"></textarea> - </div> - </form> - <!-- modal footer --> - <div class="modal-footer"> - <button id="btnSave" type="button" class="btn btn-success">Speichern</button> + <!-- modal body --> + <div class="modal-body"> + <form id="annotation-create-form"> + <div class="form-group"> + <label for="annotation-form-title" class="col-form-label">Titel:</label> + <input type="text" class="form-control" id="annotation-form-title" name="title"> + </div> + <div class="form-group"> + <label for="annotation-form-comment" class="col-form-label">Kommentar:</label> + <textarea class="form-control resize-vertical" id="annotation-form-comment" + name="comment"></textarea> </div> + </form> + <!-- modal footer --> + <div class="modal-footer"> + <button id="btnSave" type="button" class="btn btn-success">Speichern</button> </div> </div> </div> </div> +</div> - <!-- annotation edit modal --> - <div id="annotation-edit-modal" class="modal fade" role="dialog"> - <div class="modal-dialog modal-dialog-centered modal-sm"> - <div class="modal-content"> +<!-- annotation edit modal --> +<div id="annotation-edit-modal" class="modal" role="dialog"> + <div class="modal-dialog modal-dialog-centered modal-sm"> + <div class="modal-content"> - <!-- modal header --> - <div class="modal-header flex"> - <h4 class="modal-title flex-one">Annotation bearbeiten</h4> - <button type="button" class="close" data-dismiss="modal">×</button> - </div> + <!-- modal header --> + <div class="modal-header flex"> + <h4 class="modal-title flex-one">Annotation bearbeiten</h4> + <button type="button" class="close" data-dismiss="modal">×</button> + </div> - <!-- modal body --> - <div class="modal-body"> - <form id="annotation-edit-form"> - <div class="form-group"> - <label for="annotation-form-title" class="col-form-label">Titel:</label> - <input type="text" class="form-control" id="annotation-edit-form-title" name="title"> - </div> - <div class="form-group"> - <label for="annotation-form-comment" class="col-form-label">Kommentar:</label> - <textarea class="form-control resize-vertical" id="annotation-edit-form-comment" - name="comment"></textarea> - </div> - </form> - <!-- modal footer --> - <div class="modal-footer"> - <button id="btnDelete" type="button" class="btn btn-danger">Löschen</button> - <button id="btnEdit" type="button" class="btn btn-success">Bearbeiten</button> + <!-- modal body --> + <div class="modal-body"> + <form id="annotation-edit-form"> + <div class="form-group"> + <label for="annotation-form-title" class="col-form-label">Titel: + <input type="text" class="form-control" id="annotation-edit-form-title" name="title"> + </label> + </div> + <div class="form-group"> + <label for="annotation-form-comment" class="col-form-label">Kommentar: + <textarea class="form-control resize-vertical" id="annotation-edit-form-comment" + name="comment"></textarea> + </label> </div> + </form> + <!-- modal footer --> + <div class="modal-footer"> + <button id="btnDelete" type="button" class="btn btn-danger">Löschen</button> + <button id="btnEdit" type="button" class="btn btn-success">Bearbeiten</button> </div> </div> </div> </div> </div> +<footer:footer/> </body> </html> diff --git a/gemeinsamforschen/src/main/webapp/annotation/create-unstructured-annotation.jsp b/gemeinsamforschen/src/main/webapp/annotation/create-unstructured-annotation.jsp index ab27a239b3bdfa56f0fe995fbeac4007c5ede6d9..337125bb570c01e59b351f5acdc154e6eac38849 100644 --- a/gemeinsamforschen/src/main/webapp/annotation/create-unstructured-annotation.jsp +++ b/gemeinsamforschen/src/main/webapp/annotation/create-unstructured-annotation.jsp @@ -38,8 +38,6 @@ <body> <menu:menu hierarchy="1"/> -<div class="col span_l_of_2"> <!-- col right--> - <headLine:headLine/> <div class="content-mainpage"> <div class="leftcolumn"> <div class="leftcontent"> @@ -67,7 +65,6 @@ </div> </div> </div> -</div> <footer:footer/> </body> diff --git a/gemeinsamforschen/src/main/webapp/annotation/css/annotationStyle.css b/gemeinsamforschen/src/main/webapp/annotation/css/annotationStyle.css index 56fe59aba7346e671cdeb28d4fd5d22c844bc1b7..7bf81f22a41ff95e6f9c311637c5eaea8a01073d 100644 --- a/gemeinsamforschen/src/main/webapp/annotation/css/annotationStyle.css +++ b/gemeinsamforschen/src/main/webapp/annotation/css/annotationStyle.css @@ -19,7 +19,7 @@ ol { width: 25%; display: flex; flex-flow: column; - overflow: scroll; + overflow: hidden; /* background-color: blue; */ } .leftcolumn { @@ -32,12 +32,18 @@ ol { .rightcontent { padding: 10px; flex: 1; - overflow: scroll; + overflow: hidden; } + +#documentText{ + overflow: hidden; +} + .leftcontent { max-height: 100%; display: flex; flex-flow: column; + overflow: hidden; /* background-color: white; */ } .spacing { diff --git a/gemeinsamforschen/src/main/webapp/annotation/css/unstructured-annotation.css b/gemeinsamforschen/src/main/webapp/annotation/css/unstructured-annotation.css index aad00e2a4143980c2b24855dc4693a84ff2f303d..7393f26abe5aae2fddd5e327ad3816871b7d45e9 100644 --- a/gemeinsamforschen/src/main/webapp/annotation/css/unstructured-annotation.css +++ b/gemeinsamforschen/src/main/webapp/annotation/css/unstructured-annotation.css @@ -17,7 +17,11 @@ body, html { height: 100%; padding: 10px; display: inline-block; - overflow: scroll; + overflow: hidden; +} + +#documentText{ + overflow:hidden; } .leftcolumn { @@ -93,6 +97,9 @@ body, html { font-size: large; overflow: hidden; } +.category-card p{ + margin-top: 7px; +} .category-card p { position: relative; diff --git a/gemeinsamforschen/src/main/webapp/annotation/js/annotationRest.js b/gemeinsamforschen/src/main/webapp/annotation/js/annotationRest.js index 76fd89c8f25a0d29e923f2e03d617e50890707df..24eeb5936923eb951914514825ce66f95b107877 100644 --- a/gemeinsamforschen/src/main/webapp/annotation/js/annotationRest.js +++ b/gemeinsamforschen/src/main/webapp/annotation/js/annotationRest.js @@ -5,8 +5,8 @@ * @param responseHandler The response handler */ function createAnnotation(annotationPostRequest, responseHandler) { - var url = "../rest/annotations/"; - var json = JSON.stringify(annotationPostRequest); + let url = "../rest/annotations/"; + let json = JSON.stringify(annotationPostRequest); $.ajax({ url: url, type: "POST", @@ -27,8 +27,8 @@ function createAnnotation(annotationPostRequest, responseHandler) { * @param responseHandler The response handler */ function alterAnnotation(id, annotationPatchRequest, responseHandler) { - var url = "../rest/annotations/" + id; - var json = JSON.stringify(annotationPatchRequest); + let url = "../rest/annotations/" + id; + let json = JSON.stringify(annotationPatchRequest); $.ajax({ url: url, type: "PATCH", @@ -47,7 +47,7 @@ function alterAnnotation(id, annotationPatchRequest, responseHandler) { * @param id The annotation id */ function deleteAnnotation(id, responseHandler) { - var url = "../rest/annotations/" + id; + let url = "../rest/annotations/" + id; $.ajax({ url: url, type: "DELETE", @@ -65,7 +65,7 @@ function deleteAnnotation(id, responseHandler) { * @param responseHandler The response handler */ function getAnnotation(id, responseHandler) { - var url = "../rest/annotations/" + id; + let url = "../rest/annotations/" + id; $.ajax({ url: url, type: "GET", @@ -85,7 +85,7 @@ function getAnnotation(id, responseHandler) { * @param responseHandler The response handler */ function getAnnotations(targetId, targetCategory, responseHandler) { - var url = "../rest/annotations/targetid/" + targetId + "/targetcategory/" + targetCategory; + let url = "../rest/annotations/targetid/" + targetId + "/targetcategory/" + targetCategory; $.ajax({ url: url, type: "GET", @@ -99,4 +99,19 @@ function getAnnotations(targetId, targetCategory, responseHandler) { responseHandler(response); } }); +} + +function finalize(){ + $.ajax({ + url: "../rest/annotations/finalize/projectName/" + + ""+getProjectName()+"/taskName/GIVE_FEEDBACK", + type: "GET", + dataType: "application/json", + contentType: "application/json", + success:function(response){ + location.href = "../project/tasks-student.jsp?projectName=" + getProjectName() + }, + error: function(a){ + } + }); } \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/annotation/js/annotationScript.js b/gemeinsamforschen/src/main/webapp/annotation/js/annotationScript.js index cfd6e397c1d9331b66b65d90b450479a3ebbd747..c4fa9f3ff3d40a35cbcb1541ba42e6cd0f55799c 100644 --- a/gemeinsamforschen/src/main/webapp/annotation/js/annotationScript.js +++ b/gemeinsamforschen/src/main/webapp/annotation/js/annotationScript.js @@ -1,21 +1,31 @@ // initialize userEmail, userColors -var userColors = new Map(); -var userColorsDark = new Map(); +let userColors = new Map(); +let userColorsDark = new Map(); // declare document text, start and end character -var startCharacter, endCharacter; +let startCharacter, endCharacter; /** * This function will fire when the DOM is ready */ -$(document).ready(function() { - var fullSubmissionId = getQueryVariable("fullSubmissionId"); - var category = getQueryVariable("category"); - - if(category == "TITEL" || category == "titel" ) { - $('#btnBack').hide(); +$(document).ready(function () { + let fullSubmissionId = getQueryVariable("fullSubmissionId"); + let category = getQueryVariable("category"); + let btnFinalize = $('#finalize'); + btnFinalize.hide(); + let btnBack = $('#btnBack'); + if (category === "TITEL" || category === "titel") { + btnBack.hide(); } - + let btnContinue = $('#btnContinue'); + if (category === "AUSWERTUNG") { + btnFinalize.show(); + btnContinue.hide(); + } + $('.close').on("click", function () { + $('#annotation-edit-modal').hide(); + $('#annotation-create-modal').hide(); + }); // fetch full submission from database getFullSubmission(getQueryVariable("fullSubmissionId"), function (response) { @@ -25,22 +35,20 @@ $(document).ready(function() { // fetch submission parts getSubmissionPart(fullSubmissionId, category, function (response) { - var body = response.body; + let body = response.body; // save body - $('#documentText').data("body", body); - var offset = 0; - for (var i = 0; i < body.length; i++) { + let documentText = $('#documentText'); + documentText.data("body", body); + let offset = 0; + for (let i = 0; i < body.length; i++) { addHighlightedSubmissionPart(body[i].startCharacter, body[i].endCharacter, offset); // add char count of '<span class="categoryText"></span>' offset += 34; } - // scroll document text to first span element - var documentText = $('#documentText'); - var span = $('#documentText span').first(); - documentText.scrollTo(span); - + let span = $('#documentText span').first(); + documentText.scrollTo(span); }, function () { // error }) @@ -57,11 +65,9 @@ $(document).ready(function() { */ $.contextMenu({ selector: '.context-menu-one', - callback: function(key, options) { - + callback: function () { // handle annotation context click handleAnnotationClick() - }, items: { "annotation": {name: "Annotation", icon: "edit"} @@ -71,44 +77,48 @@ $(document).ready(function() { /** * continue button */ - $('#btnContinue').click(function () { + btnContinue.click(function () { - var submissionId = getQueryVariable("fullSubmissionId"); - var category = getQueryVariable("category"); - var nextCategory = calculateNextCategory(category); + let submissionId = getQueryVariable("fullSubmissionId"); + let category = getQueryVariable("category"); + let nextCategory = calculateNextCategory(category); - if (!nextCategory) { - // TODO go to feedback page - location.href = "../project/give-feedback.jsp?projectName=" + getProjectName(); - } - else { - location.href = "../annotation/annotation-document.jsp?fullSubmissionId="+submissionId + "&category="+nextCategory; + if (nextCategory) { + location.href = "../annotation/annotation-document.jsp?" + + "projectName=" + getProjectName() + + "&fullSubmissionId=" + submissionId + + "&category=" + nextCategory; } }); + btnFinalize.on("click", function () { + finalize(); + }); + + /** * back button */ - $('#btnBack').click(function () { + btnBack.click(function () { - var submissionId = getQueryVariable("fullSubmissionId"); - var category = getQueryVariable("category"); - var nextCategory = calculateLastCategory(category); + let submissionId = getQueryVariable("fullSubmissionId"); + let category = getQueryVariable("category"); + let nextCategory = calculateLastCategory(category); if (!nextCategory) { } else { - location.href = "../annotation/annotation-document.jsp?fullSubmissionId="+submissionId + "&category="+nextCategory; + location.href = "../annotation/annotation-document.jsp?fullSubmissionId=" + submissionId + "&category=" + nextCategory; } }); function calculateNextCategory(current) { - var categories = ["TITEL", "RECHERCHE", "LITERATURVERZEICHNIS", "FORSCHUNGSFRAGE", "UNTERSUCHUNGSKONZEPT", "METHODIK", "DURCHFUEHRUNG", "AUSWERTUNG"]; - var result = false; - for (var i = 0; i< categories.length -1; i++) { - if (categories[i] == current) { + let categories = ["TITEL", "RECHERCHE", "LITERATURVERZEICHNIS", "FORSCHUNGSFRAGE", "UNTERSUCHUNGSKONZEPT", "METHODIK", "DURCHFUEHRUNG", "AUSWERTUNG"]; + let result = false; + for (let i = 0; i < categories.length - 1; i++) { + if (categories[i] === current) { result = categories[i + 1]; } } @@ -117,10 +127,10 @@ $(document).ready(function() { } function calculateLastCategory(current) { - var categories = ["TITEL", "RECHERCHE", "LITERATURVERZEICHNIS", "FORSCHUNGSFRAGE", "UNTERSUCHUNGSKONZEPT", "METHODIK", "DURCHFUEHRUNG", "AUSWERTUNG"]; - var result = false; - for (var i = 1; i< categories.length; i++) { - if (categories[i] == current) { + let categories = ["TITEL", "RECHERCHE", "LITERATURVERZEICHNIS", "FORSCHUNGSFRAGE", "UNTERSUCHUNGSKONZEPT", "METHODIK", "DURCHFUEHRUNG", "AUSWERTUNG"]; + let result = false; + for (let i = 1; i < categories.length; i++) { + if (categories[i] === current) { result = categories[i - 1]; } } @@ -129,8 +139,6 @@ $(document).ready(function() { } - - /** * validation of annotation create form inside the modal */ @@ -190,17 +198,25 @@ $(document).ready(function() { $('#btnSave').click(function () { if ($('#annotation-create-form').valid()) { // get title and comment from form - var title = $('#annotation-form-title').val(); - var comment = $('#annotation-form-comment').val(); + let title = $('#annotation-form-title').val(); + let comment = $('#annotation-form-comment').val(); // hide and clear the modal - $('#annotation-create-modal').modal('hide'); + $('#annotation-create-modal').hide(); // save the new annotation in db and display it saveNewAnnotation(title, comment, startCharacter, endCharacter); } }); + $('#btnWholeCategory').click(function () { + getSubmissionPart(fullSubmissionId, category, function () { + selectText(); + handleAnnotationClick(); + + }); + }); + /** * Edit button of the annotation edit modal * hide modal and alter the annotation @@ -208,25 +224,25 @@ $(document).ready(function() { $('#btnEdit').click(function () { if ($('#annotation-edit-form').valid()) { // get title and comment from clicked annotation card - var id = $('#annotation-edit-modal').data('id'); - var card = $('#' + id); - var title = card.find('.annotation-header-data-title').text(); - var comment = card.find('.annotation-body-text').text(); + let id = $('#annotation-edit-modal').data('id'); + let card = $('#' + id); + let title = card.find('.annotation-header-data-title').text(); + let comment = card.find('.annotation-body-text').text(); // get title and comment from form - var newTitle = $('#annotation-edit-form-title').val(); - var newComment = $('#annotation-edit-form-comment').val(); + let newTitle = $('#annotation-edit-form-title').val(); + let newComment = $('#annotation-edit-form-comment').val(); // compare new and old card content if (title !== newTitle || comment !== newComment) { // build patch request - var annotationPatchRequest = { + let annotationPatchRequest = { title: newTitle, comment: newComment }; // send alter request to server - alterAnnotation(id, annotationPatchRequest, function (response) { + alterAnnotation(id, annotationPatchRequest, function () { // send altered annotation to websocket send("EDIT", id); @@ -245,30 +261,30 @@ $(document).ready(function() { }); /** - * Devare an annotation from list and server + * Delete an annotation from list and server */ - $('#btnDevare').click(function () { + $('#btnDelete').click(function () { // get id from edit modal - var id = $('#annotation-edit-modal').data('id'); + let id = $('#annotation-edit-modal').data('id'); // delte annotation from server and from list - devareAnnotation(id, function () { - // send devare request to websocket - send("DEvarE", id); + deleteAnnotation(id, function () { + // send delete request to websocket + send("DELETE", id); // remove annotation from list - $('#' + id).closest('.listelement').remove() + $('#' + id).closest('.listelement').remove(); // remove highlighted text - devareHighlightedText(); + deleteHighlightedText(); // hide and clear the modal - $('#annotation-edit-modal').modal('hide'); + $('#annotation-edit-modal').hide(); }) }); /** * Clear the title and comment input field of the create modal */ - $('#annotation-create-modal').on('hidden.bs.modal', function(){ + $('#annotation-create-modal').on('hidden.bs.modal', function () { // clear title $('#annotation-form-title').val(''); // clear comment @@ -278,7 +294,7 @@ $(document).ready(function() { /** * Clear the title and comment input field of the edit modal */ - $('#annotation-edit-modal').on('hidden.bs.modal', function(e){ + $('#annotation-edit-modal').on('hidden.bs.modal', function () { // clear title $('#annotation-edit-form-title').val(''); // clear comment @@ -290,7 +306,7 @@ $(document).ready(function() { // iterate over annotations and display each $.each(response, function (i, annotation) { displayAnnotation(annotation); - }) + }); // handle drop down button showAndHideToggleButton(); }); @@ -300,7 +316,7 @@ $(document).ready(function() { /** * This will be called on page resize */ -$( window ).resize(function() { +$(window).resize(function () { // handle drop down button for every annotation showAndHideToggleButton(); }); @@ -312,16 +328,16 @@ $( window ).resize(function() { */ function displayAnnotation(annotation) { // fetch list of annotations - var list = $('#annotations') + let list = $('#annotations'); - var editIcon = "fas fa-edit"; - var dateIcon = "fas fa-calendar"; + let editIcon = "fas fa-edit"; + let dateIcon = "fas fa-calendar"; if (isTimestampToday(annotation.timestamp)) { dateIcon = "fas fa-clock"; } // declare variables - var display, input, filter, user, title, comment; + let display, input, filter, user, title, comment; input = $('#annotation-search'); filter = input.val().toLowerCase(); @@ -425,11 +441,9 @@ function displayAnnotation(annotation) { $('<i>').attr('class', dateIcon) ) .append( - $('<span>').append(timestampToReadabvarime(annotation.timestamp)) + $('<span>').append(timestampToReadableTime(annotation.timestamp)) ) ) - - ) ) .data('annotation', annotation) @@ -437,12 +451,12 @@ function displayAnnotation(annotation) { addHighlightedAnnotation(annotation.body.startCharacter, annotation.body.endCharacter, annotation.userEmail); // scroll document text to anchor element - var documentText = $('#documentText'); - var anchor = $('#anchor'); + let documentText = $('#documentText'); + let anchor = $('#anchor'); documentText.scrollTo(anchor); }) .mouseleave(function () { - devareHighlightedText(); + deleteHighlightedText(); }) .append( $('<div>').attr('class', 'spacing') @@ -458,23 +472,24 @@ function displayAnnotation(annotation) { * @param userEmail The user token */ function addHighlightedAnnotation(startCharacter, endCharacter, userEmail) { - var offset = calculateExtraOffset(startCharacter); + let offset = calculateExtraOffset(startCharacter); - // initialize variables - var documentText = $('#documentText').text(); - var documentHtml = $('#documentText').html(); + //initialize variables + let docText = $('#documentText'); + let documentText = docText.text(); + let documentHtml = docText.html(); - // create <span> tag with the annotated text - var replacement = $('<span></span>').attr('id', 'anchor').css('background-color', getUserColor(userEmail)).html(documentText.slice(startCharacter, endCharacter)); + //create <span> tag with the annotated text + let replacement = $('<span></span>').attr('id', 'anchor').css('background-color', getUserColor(userEmail)).html(documentText.slice(startCharacter, endCharacter)); - // wrap an <p> tag around the replacement, get its parent (the <p>) and ask for the html - var replacementHtml = replacement.wrap('<p/>').parent().html(); + //wrap an <p> tag around the replacement, get its parent (the <p>) and ask for the html + let replacementHtml = replacement.wrap('<p/>').parent().html(); - // insert the replacementHtml - var newDocument = documentHtml.slice(0, startCharacter + offset) + replacementHtml + documentHtml.slice(endCharacter + offset); + //insert the replacementHtml + let newDocument = documentHtml.slice(0, startCharacter + offset) + replacementHtml + documentHtml.slice(endCharacter + offset); // set new document text - $('#documentText').html(newDocument); + docText.html(newDocument); } /** @@ -486,20 +501,21 @@ function addHighlightedAnnotation(startCharacter, endCharacter, userEmail) { */ function addHighlightedSubmissionPart(startCharacter, endCharacter, offset) { - var documentText = $('#documentText').text(); - var documentHtml = $('#documentText').html(); + let docText = $('#documentText'); + let documentText = docText.text(); + let documentHtml = docText.html(); // create <span> tag with the annotated text - var replacement = $('<span></span>').attr('class', 'categoryText').html(documentText.slice(startCharacter, endCharacter)); + let replacement = $('<span></span>').attr('class', 'categoryText').html(documentText.slice(startCharacter, endCharacter)); // wrap an <p> tag around the replacement, get its parent (the <p>) and ask for the html - var replacementHtml = replacement.wrap('<p/>').parent().html(); + let replacementHtml = replacement.wrap('<p/>').parent().html(); // insert the replacementHtml - var newDocument = documentHtml.slice(0, startCharacter + offset) + replacementHtml + documentHtml.slice(endCharacter + offset); + let newDocument = documentHtml.slice(0, startCharacter + offset) + replacementHtml + documentHtml.slice(endCharacter + offset); // set new document text - $('#documentText').html(newDocument); + docText.html(newDocument); } /** @@ -510,8 +526,8 @@ function addHighlightedSubmissionPart(startCharacter, endCharacter, offset) { */ function calculateExtraOffset(startCharacter) { // get submission part body - var body = $('#documentText').data("body"); - var extraOffset = 0; + let body = $('#documentText').data("body"); + let extraOffset = 0; for (var i = 0; i < body.length; i++) { if (body[i].startCharacter <= startCharacter) { @@ -528,11 +544,11 @@ function calculateExtraOffset(startCharacter) { /** * Restore the base text */ -function devareHighlightedText() { +function deleteHighlightedText() { - var documentText = $('#documentText'); - var highlight = documentText.find('#anchor'); - var text = highlight.text(); + let documentText = $('#documentText'); + let highlight = documentText.find('#anchor'); + let text = highlight.text(); highlight.replaceWith(text); } @@ -543,13 +559,13 @@ function devareHighlightedText() { * @returns {string} The text */ function getSelectedText() { - if(window.getSelection){ + if (window.getSelection) { return window.getSelection().toString(); } - else if(document.getSelection){ + else if (document.getSelection) { return document.getSelection(); } - else if(document.selection){ + else if (document.selection) { return document.selection.createRange().text; } } @@ -590,15 +606,15 @@ function getDarkUserColor(userEmail) { * @param userEmail The given user token */ function generateRandomColor(userEmail) { - var r = Math.floor(Math.random()*56)+170; - var g = Math.floor(Math.random()*56)+170; - var b = Math.floor(Math.random()*56)+170; - var r_d = r - 50; - var g_d = g - 50; - var b_d = b - 50; + let r = Math.floor(Math.random() * 56) + 170; + let g = Math.floor(Math.random() * 56) + 170; + let b = Math.floor(Math.random() * 56) + 170; + let r_d = r - 50; + let g_d = g - 50; + let b_d = b - 50; - var color = 'rgb(' + r + ',' + g + ',' + b + ')'; - var colorDark = 'rgb(' + r_d + ',' + g_d + ',' + b_d + ')'; + let color = 'rgb(' + r + ',' + g + ',' + b + ')'; + let colorDark = 'rgb(' + r_d + ',' + g_d + ',' + b_d + ')'; userColors.set(userEmail, color); userColorsDark.set(userEmail, colorDark); @@ -610,16 +626,16 @@ function generateRandomColor(userEmail) { * @param timestamp A unix timestamp * @returns {string} A readable timestamp */ -function timestampToReadabvarime(timestamp) { +function timestampToReadableTime(timestamp) { // build Date object from timestamp - var annotationDate = new Date(timestamp); + let annotationDate = new Date(timestamp); // declare response - var responseTimestamp; + let responseTimestamp; // get hours from date - var hours = "0" + annotationDate.getHours(); + let hours = "0" + annotationDate.getHours(); // get minutes from date - var minutes = "0" + annotationDate.getMinutes(); + let minutes = "0" + annotationDate.getMinutes(); // if annotation is from today if (isTimestampToday(timestamp)) { @@ -629,11 +645,11 @@ function timestampToReadabvarime(timestamp) { // else annotation is not from today else { // get date - var date = "0" + annotationDate.getDate(); + let date = "0" + annotationDate.getDate(); // get month - var month = "0" + annotationDate.getMonth(); + let month = "0" + annotationDate.getMonth(); // get year - var year = "" + annotationDate.getFullYear(); + let year = "" + annotationDate.getFullYear(); // build readable timestamp dd.MM.yy HH:mm responseTimestamp = date.substr(-2) + "." + month.substr(-2) + "." + year.substr(-2) + " " + hours.substr(-2) + ":" + minutes.substr(-2); @@ -650,17 +666,12 @@ function timestampToReadabvarime(timestamp) { */ function isTimestampToday(timestamp) { // now - var now = new Date(); + let now = new Date(); // build Date object from timestamp - var date = new Date(timestamp); + let date = new Date(timestamp); // return true if timestamp is today - if (now.getDate() == date.getDate() && now.getMonth() == date.getMonth() && now.getFullYear() == date.getFullYear()) { - return true; - } - else { - return false; - } + return (now.getDate() === date.getDate() && now.getMonth() === date.getMonth() && now.getFullYear() === date.getFullYear()); } /** @@ -670,7 +681,7 @@ function isTimestampToday(timestamp) { */ function toggleButtonHandler(id) { // the clicked annotation card - var card = $('#' + id); + let card = $('#' + id); // open and close annotation text card.find(".annotation-body").children("p").toggleClass("overflow-hidden"); // toggle between up and down button @@ -688,12 +699,12 @@ function toggleButtonHandler(id) { function saveNewAnnotation(title, comment, startCharacter, endCharacter) { // initialize target - var targetId = getQueryVariable("fullSubmissionId"); - var targetCategory = getQueryVariable("category").toUpperCase(); - var userEmail = getUserEmail(); + let targetId = getQueryVariable("fullSubmissionId"); + let targetCategory = getQueryVariable("category").toUpperCase(); + let userEmail = getUserEmail(); // build annotationPostRequest - var annotationPostRequest = { + let annotationPostRequest = { userEmail: userEmail, targetId: targetId, targetCategory: targetCategory, @@ -706,7 +717,7 @@ function saveNewAnnotation(title, comment, startCharacter, endCharacter) { }; // send new annotation to back-end and display it in list - createAnnotation(annotationPostRequest, function(response) { + createAnnotation(annotationPostRequest, function (response) { // send new annotation to websocket send("CREATE", response.id); // display the new annotation @@ -722,17 +733,17 @@ function saveNewAnnotation(title, comment, startCharacter, endCharacter) { */ function editAnnotationHandler(id) { // the clicked annotation card - var card = $('#' + id); + let card = $('#' + id); // get title and comment - var title = card.find('.annotation-header-data-title').text(); - var comment = card.find('.annotation-body-text').text(); + let title = card.find('.annotation-header-data-title').text(); + let comment = card.find('.annotation-body-text').text(); // set title and comment $('#annotation-edit-form-title').val(title); $('#annotation-edit-form-comment').val(comment); // display annotation edit modal and pass id - $('#annotation-edit-modal').data('id', id).modal("show"); + $('#annotation-edit-modal').data('id', id).show(); } /** @@ -742,7 +753,7 @@ function editAnnotationHandler(id) { */ function editAnnotationValues(annotation) { // find annotation - var annotationElement = $('#' + annotation.id); + let annotationElement = $('#' + annotation.id); // set title and comment annotationElement.find('.annotation-header-data-title').text(annotation.body.title); @@ -761,17 +772,17 @@ function showAndHideToggleButton() { $('#annotations').find('li').each(function () { // find the comment element, clone and hide it - var comment = $(this).find('.annotation-body').children('p'); - var clone = comment.clone() + let comment = $(this).find('.annotation-body').children('p'); + let clone = comment.clone() .css({display: 'inline', width: 'auto', visibility: 'hidden'}) .appendTo('body'); - var cloneWidth = clone.width(); + let cloneWidth = clone.width(); // remove the element from the page clone.remove(); // show drop down button only if text was truncated - if(cloneWidth > comment.width()) { + if (cloneWidth > comment.width()) { $(this).find('.annotation-header-toggle').show(); $(this).find('.annotation-header-data').css('width', 'calc(100% - 40px)'); } @@ -790,19 +801,19 @@ function showAndHideToggleButton() { */ function showAndHideToggleButtonById(id) { // find annotation - var annotationElement = $('#' + id); + let annotationElement = $('#' + id); // find the comment element, clone and hide it - var comment = annotationElement.find('.annotation-body').children('p'); - var clone = comment.clone() + let comment = annotationElement.find('.annotation-body').children('p'); + let clone = comment.clone() .css({display: 'inline', width: 'auto', visibility: 'hidden'}) .appendTo('body'); - var cloneWidth = clone.width(); + let cloneWidth = clone.width(); // remove the element from the page clone.remove(); // show drop down button only if text was truncated - if(cloneWidth > comment.width()) { + if (cloneWidth > comment.width()) { annotationElement.find('.annotation-header-toggle').show(); annotationElement.find('.annotation-header-data').css('width', 'calc(100% - 40px)'); } @@ -819,14 +830,14 @@ function showAndHideToggleButtonById(id) { function handleAnnotationClick() { // if saved selection's range count is > 0 - var sel = rangy.getSelection(); + let sel = rangy.getSelection(); if (sel.rangeCount > 0) { // calculate character range offset from range - var range = sel.getRangeAt(0); - var offsets = range.toCharacterRange($('#documentText')[0]); + let range = sel.getRangeAt(0); + let offsets = range.toCharacterRange($('#documentText')[0]); // if selected text's length is > 0 - var selectedText = getSelectedText(); + let selectedText = getSelectedText(); if (selectedText.length > 0) { // save start and end character and handle the selection startCharacter = offsets.start; @@ -834,18 +845,20 @@ function handleAnnotationClick() { if (isAnnotationInRange(startCharacter, endCharacter)) { // display annotation create modal - $('#annotation-create-modal').modal("show"); + $('#annotation-create-modal').show(); } else { window.alert("Annotationen sind nur in vorgehobenen Bereichen möglich") } - - + } else { + $('#annotation-create-modal').show(); } + } else { + $('#annotation-create-modal').show(); } - } + /** * Checks if user selected area is inside submission part range * @@ -854,8 +867,8 @@ function handleAnnotationClick() { * @returns {boolean} Returns true if the selection is in range */ function isAnnotationInRange(start, end) { - var body = $('#documentText').data("body"); - for (var i = 0; i < body.length; i++) { + let body = $('#documentText').data("body"); + for (let i = 0; i < body.length; i++) { if (body[i].startCharacter <= start && end <= body[i].endCharacter) { return true; } @@ -868,13 +881,13 @@ function isAnnotationInRange(start, end) { */ function searchAnnotation() { // declare variables - var input, filter; + let input, filter; input = $('#annotation-search'); filter = input.val().toLowerCase(); // iterate over annotation card and hide those who don't match the search query $('#annotations').find('li').each(function () { - var user, title, comment; + let user, title, comment; user = $(this).find('.annotation-header-data-user').text().toLowerCase(); title = $(this).find('.annotation-header-data-title').text().toLowerCase(); comment = $(this).find('.annotation-body-text').text().toLowerCase(); @@ -889,5 +902,15 @@ function searchAnnotation() { $(this).css('display', 'none') } }); +} +function selectText() { + let text = document.getElementsByClassName('categoryText')[0]; + if (window.getSelection()){ + let selection = window.getSelection(); + let range = document.createRange(); + range.selectNodeContents(text); + selection.removeAllRanges(); + selection.addRange(range); + } } diff --git a/gemeinsamforschen/src/main/webapp/annotation/js/annotationWebsocket.js b/gemeinsamforschen/src/main/webapp/annotation/js/annotationWebsocket.js index f407ad85bf27f28c6b2a5e510e6a3a05d8c5665f..6e00409849b849df3e140405848e84c8e7bb0f22 100644 --- a/gemeinsamforschen/src/main/webapp/annotation/js/annotationWebsocket.js +++ b/gemeinsamforschen/src/main/webapp/annotation/js/annotationWebsocket.js @@ -1,13 +1,12 @@ -var ws; +let ws; function connect(targetId, targetCategory) { - var host = document.location.host; - var pathname = document.location.pathname; + let host = document.location.host; ws = new WebSocket("ws://" + host + "/gemeinsamforschen/ws/annotation/" + targetId + "/" + targetCategory); ws.onmessage = function (e) { - var message = JSON.parse(e.data); + let message = JSON.parse(e.data); if (message.type === "CREATE") { // get annotation from server @@ -29,10 +28,10 @@ function connect(targetId, targetCategory) { } function send(type, annotationId) { - var json = JSON.stringify({ + let json = JSON.stringify({ "type":type, "annotationId":annotationId - }) + }); ws.send(json); } \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/annotation/js/createJournal.js b/gemeinsamforschen/src/main/webapp/annotation/js/createJournal.js index 731ae078e22e5c184c9df4ba3810a0af10f73e6a..ce2db8e56c0fe51a7efabac8ce87cbb049f35bde 100644 --- a/gemeinsamforschen/src/main/webapp/annotation/js/createJournal.js +++ b/gemeinsamforschen/src/main/webapp/annotation/js/createJournal.js @@ -8,7 +8,7 @@ $(document).ready(function() { location.href = "eportfolio.jsp"; }); - var journalID = getQueryVariable("journal"); + let journalID = getQueryVariable("journal"); console.log(journalID); if(journalID){ @@ -39,4 +39,4 @@ $(document).ready(function() { } -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/annotation/js/unstructuredAnnotation.js b/gemeinsamforschen/src/main/webapp/annotation/js/unstructuredAnnotation.js index 4964685f40f85957c657686a3162f5fe5b7a7ee3..33c77a264e66c1cfcdf0c424c312ac69f475304f 100644 --- a/gemeinsamforschen/src/main/webapp/annotation/js/unstructuredAnnotation.js +++ b/gemeinsamforschen/src/main/webapp/annotation/js/unstructuredAnnotation.js @@ -81,7 +81,7 @@ function handleCategorySelection(category, startCharacter, endCharacter) { if (!isAlreadyHighlighted(startCharacter, endCharacter)) { // check if element has 'not-added' class - var elem = $('#' + category); + let elem = $('#' + category); if (elem.hasClass("not-added")) { elem.toggleClass("not-added added-" + category); } diff --git a/gemeinsamforschen/src/main/webapp/annotation/upload-unstructured-dossier.jsp b/gemeinsamforschen/src/main/webapp/annotation/upload-unstructured-dossier.jsp index 566f771f57719b149c6384e8a61fa7b34176f56d..0189cc43cb2f102a9934f5e7b757841de4c92761 100644 --- a/gemeinsamforschen/src/main/webapp/annotation/upload-unstructured-dossier.jsp +++ b/gemeinsamforschen/src/main/webapp/annotation/upload-unstructured-dossier.jsp @@ -1,4 +1,4 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> +<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> @@ -12,10 +12,9 @@ <omniDependencies:omniDependencies hierarchy="1"/> <!-- js - jQuery validation plugin --> - <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.14.0/jquery.validate.min.js"></script> + <script src="../libs/jquery/jqueryValidate.js"></script> <!-- js - jQuery ui position --> - <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js" - type="text/javascript"></script> + <script src="../libs/jquery/jqueryUI.js" type="text/javascript"></script> <!-- css - upload-unstructured --> <link rel="stylesheet" type="text/css" href="css/unstructured-upload.css"> @@ -28,8 +27,6 @@ <body> <menu:menu hierarchy="1"/> -<div class="col span_l_of_2"> <!-- col right--> - <headLine:headLine/> <form id="upload-textarea-form"> <div class="form-group upload-text" id="documentText"> <label for="upload-textarea">Texteingabe</label> @@ -48,7 +45,6 @@ <button type="button" class="btn btn-primary document-text-buttons-next" id="btnNext">Weiter </button> </div> -</div> <footer:footer/> </body> diff --git a/gemeinsamforschen/src/main/webapp/feedback/css/viewfeedback.css b/gemeinsamforschen/src/main/webapp/feedback/css/viewfeedback.css deleted file mode 100644 index 65f5d525908983d27cc2eee7df001a4817bbf372..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/webapp/feedback/css/viewfeedback.css +++ /dev/null @@ -1,42 +0,0 @@ -.feedback-container { - border: 2px solid #dedede; - background-color: #f1f1f1; - border-radius: 5px; - padding: 10px; - margin: 10px 0; -} - -.darker { - border-color: #ccc; - background-color: #ddd; -} - -.feedback-container::after { - content: ""; - clear: both; - display: table; -} - -.feedback-container img { - float: left; - max-width: 60px; - width: 100%; - margin-right: 20px; - border-radius: 50%; -} - -.feedback-container img.right { - float: right; - margin-left: 20px; - margin-right: 0; -} - -.time-right { - float: right; - color: #aaa; -} - -.time-left { - float: left; - color: #999; -} \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/feedback/give-feedback.jsp b/gemeinsamforschen/src/main/webapp/feedback/give-feedback.jsp deleted file mode 100644 index 4a554ea1f49adca3cd387314f8d573b3fafb864e..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/webapp/feedback/give-feedback.jsp +++ /dev/null @@ -1,125 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> - -<!DOCTYPE html> -<html> - -<head> - - <script src="js/jquery.min.js"></script> - <script src="../assets/bootstrap/js/bootstrap.min.js"></script> - <script src="js/Sidebar-Menu.js"></script> - <script src="js/create-journal.js"></script> - --%> - - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/inscrybmde@1.11.3/dist/inscrybmde.min.css"> - <script src="https://cdn.jsdelivr.net/npm/inscrybmde@1.11.3/dist/inscrybmde.min.js"></script> - <link rel="stylesheet" type="text/css" href="../journal/css/create-journal.css"> - <script src="js/peerfeedback.js"></script> - <omniDependencies:omniDependencies hierarchy="1"/> -</head> - -<body> -<menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <headLine:headLine/> - <div> - <table> - <tr> - <h2> Gib dein Feedback ein!</h2> - <div class="line-spacer"></div> - <p><span> Datei zum Feedback: SelectedFile.pdf </span></p> - <p class="text-primary"><span> Kategorie: Untersuchungskonzept </span></p> - <hr/> - <div class="form-check"> - <input class="form-check-input" type="checkbox" value="" id="defaultCheck1"> - <label class="form-check-label" for="defaultCheck1"> - Das fand ich gut - </label> - </div> - <div class="form-check"> - <input class="form-check-input" type="checkbox" value="" id="defaultCheck2"> - <label class="form-check-label" for="defaultCheck1"> - Ich habe noch eine Frage - </label> - </div> - <div class="form-check"> - <input class="form-check-input" type="checkbox" value="" id="defaultCheck3"> - <label class="form-check-label" for="defaultCheck1"> - Das wuerde ich anders machen - </label> - </div> - <div class="form-check"> - <input class="form-check-input" type="checkbox" value="" id="defaultCheck4"> - <label class="form-check-label" for="defaultCheck1"> - Ich habe eine Idee - </label> - </div> - <hr/> - - <div> - <table> - <tr> - <td id="yourContent"> - <h2> Schreibe dein Feedback! </h2> - - <form id="form" method="POST" action="../rest/peerfeedback/save"> - <%--id="journalform" class="form-journal"--%> - <input type="hidden" id="student" name="student"> - <input type="hidden" id="project" name="project"> - <input type="hidden" id="feedbackid" name="id"> - <input type="hidden" id="reciever" name="reciever"> - <input type="hidden" id="sender" name="sender"> - <input type="hidden" id="filename" name="filename"> - <input type="hidden" id="category" name="category"> - <input type="hidden" id="filename" name="filename"> - - - <div class="journal-form-container"> - - <div class="journal-form-editor"> - <textarea id="editor" name="text"></textarea> <%--form="journalform"--%> - </div> - - <div class="journal-form-buttons"> - <input class="btn btn-default btn-sm" type="submit"> - <a id="backLink" class="btn btn-default btn-sm"> Zurück </a> - </div> - - <div> - <p id="as">Now what</p> - <input type="button" value="get txt" onclick="go()"/> - </div> - - </div> - </form> - - - </td> - </tr> - </table> - </div> - - - <script> - function goBack() { - window.history.back(); - } - </script> - </tr> - </td> - - - </tr> - </table> - </div> - </div> - <footer:footer/> -</div> -</body> - -</html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/feedback/js/givepeerfeedback.js b/gemeinsamforschen/src/main/webapp/feedback/js/givepeerfeedback.js deleted file mode 100644 index 2444caf95c3be8f8d6ce764353b940842ae5050b..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/webapp/feedback/js/givepeerfeedback.js +++ /dev/null @@ -1,83 +0,0 @@ -var student = getQueryVariable("token"); -var project = getQueryVariable("projectName"); - -$(document).ready(function () { - $('#student').val(student); - $('#project').val(project); - - - var peerfeedbackID = getQueryVariable("Peerfeedback"); - console.log(peerfeedbackID); - var peerfeedbackID = "a3cef66d-e1b7-4030-8fcd-1413d6e77ba0"; - var sender = "sender"; - console.log(peerfeedbackID); - //if(peerfeedbackID) { - $.ajax({ - url: "../rest/peerfeedback/" + sender //peerfeedbackID {id} - - }).then(function (data) { - //$('#editor').append(data.descriptionMD); - console.log("function1"); - loadFeedback(data); - console.log("function2"); - - //document.getElementById("Peerfeedback").innerHTML = data.text +"text"; - //document.write(data); - //console.log(data); - /** - var newDiv = document.createElement("div"); - var newContent = document.createTextNode(data.text); - newDiv.appendChild(newContent); // füge den Textknoten zum neu erstellten div hinzu. - - // füge das neu erstellte Element und seinen Inhalt ins DOM ein - var currentDiv = document.getElementById("div1"); - currentDiv.appendChild(newDiv); - //document.body.insertBefore(newDiv, currentDiv); - */ - //$('#peerfeedbackID').val(peerfeedbackID); - console.log(data); - }); - - // } - function loadFeedback(data) { - for (var feedback in data) { - /**var feedbackString = '<div class="pf-container">' + - '<div class="journal-date"> ' + - data[feedback].timestamp + - '</div>' + - '<div class="journal-name">' + - // TODO id to name - data[feedback].text + - '</div>' + - '<div class="journal-category">' + - data[feedback].id + - '</div>' + - '<div class="journal-edit" align="right">'; - - feedbackString = feedbackString + '</div>' + - '<div class="journal-text">' + - data[feedback].entryHTML + - '</div>' + - '</div><br><br>';*/ - - var newdiv = document.createElement("div"); - - - //newdiv.innerHTML = data[feedback].text; - //newdiv.append(data[feedback].text); - newdiv.insertAdjacentHTML('beforeend', data[feedback].text); - newdiv.className = "feedback-container"; - //var text = convertMarkdownToHtml(data[feedback].text); - //var newcontent = document.createTextNode(data[feedback].text); - //newdiv.appendChild(newcontent); // füge den Textknoten zum neu erstellten div hinzu. - - // füge das neu erstellte Element und seinen Inhalt ins DOM ein - var currentdiv = document.getElementById("div1"); - currentdiv.appendChild(newdiv); - //document.body.insertBefore(newDiv, currentDiv); - //document.getElementById("div").innerHTML = data[feedback].text; - - //$('.Peerfeedback').append(feedbackString) - } - }; -}) \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/feedback/js/peerfeedback.js b/gemeinsamforschen/src/main/webapp/feedback/js/peerfeedback.js deleted file mode 100644 index a12ccc7c60f927b292570468590b112019c1307b..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/webapp/feedback/js/peerfeedback.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * POST: Save an peerfeedback in the database - * - * @param Peer2PeerFeedback The post request - * @param responseHandler The response handler - */ -/**function createPeerfeedback(Peer2PeerFeedback, responseHandler) { - var url = "../rest/peerfeedback/save"; - var json = JSON.stringify(Peer2PeerFeedback); - $.ajax({ - url: url, - type: "POST", - data: json, - contentType: "application/json", - dataType: "json", - success: function (response) { - responseHandler(response); - } - }); -}*/ - -var student = getQueryVariable("token"); -var project = getQueryVariable("projectName"); - -function go() { - var a = document.getElementById("editor").valueOf().toString(); - var b = document.getElementById("as"); - b.innerHTML = a.toString(); - - //b = document.getElementById("editor").innerHTML; - - -} - -$(document).ready(function () { - $('#student').val(student); - $('#project').val(project); - - $.ajax({ - url: "../rest/peerfeedback/save" - }).then(function (data) { - $('#editor').append(data.descriptionMD); - - /** //TODO preselet in select tags - new InscrybMDE({ - element: document.getElementById("editor"), - spellChecker: false, - forceSync: true, - });*/ - - //console.log(data); - location.href = "give-feedback.jsp" + getUserEmail(); - alert("Feedback wurde gesendet!"); - }); -}) \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/feedback/view-feedback.jsp b/gemeinsamforschen/src/main/webapp/feedback/view-feedback.jsp deleted file mode 100644 index 3ff1324a47d437f6c900e2d0a719fd446d21b206..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/webapp/feedback/view-feedback.jsp +++ /dev/null @@ -1,170 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> - -<!DOCTYPE html> -<html> - -<head> - <link rel="stylesheet" href="css/viewfeedback.css"> - <omniDependencies:omniDependencies hierarchy="1"/> - <script src="js/givepeerfeedback.js"></script> - -</head> - -<body> -<menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <headLine:headLine/> - <div class="container-fluid"> - <h1 id="projectName"> PeerFeedback</h1> - </div> - <div align="right" class="dropdown"> - <button style= "position: absolute; right: 50px;" class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown"> - - <i class="glyphicon glyphicon-envelope"></i> - </button> - - <ul class="dropdown-menu"> - <li><a class="viewfeedback" role="button">Feedback A</a></li> - <li><a class="viewfeedback" role="button">Feedback B</a></li> - <li><a class="viewfeedback" role="button">Feedback C</a></li> - </ul> - - <a href="#"> - <span class="glyphicon glyphicon-cog" style="font-size:29px;margin-right:30px;margin-top:3px;"></span> - </a> - - </div> - <div> - <table> - <tr> - <tr> - <th>Feedback Nachrichten von Student X</th> - </tr> - - - <td id="filter-feedbacks"> - - - <%--<iframe width="90%" height="200%" src="http://rocketchat.westeurope.cloudapp.azure.com/channel/general?layout=embedded"></iframe> - --%> - <%--<p id="view"></p> type="hidden"--%> - <input type="hidden" name="peerfeedbackID" id="peerfeedbackID-input" value=""/> - <div style="height: 100px; overflow: auto"> - <div class="feedback-container"> - <p>Sender</p> - <span class="time-right">11:00</span> - </div> - </div> - - - </td> - - - <td id="view-feedbacks"> - - <div style="height: 300px; overflow: auto"> - - <div class="feedback-container"> - <p>Hello. How are you today?</p> - <span class="time-right">11:00</span> - </div> - - <div class="feedback-container"> - <p>Hey! I'm fine. Thanks for asking!</p> - <span class="time-left">11:01</span> - </div> - - <div class="feedback-container"> - <p>Sweet! So, what do you wanna do today?</p> - <span class="time-right">11:02</span> - </div> - - <div id="div1"></div> - - </div> - </td> - - - <button class="btn btn-secondary" onclick="goBack()">Zurück</button> - - <script> - function goBack() { - window.history.back(); - } - </script> - - </td> - - - <td id="chat"> - <div class="card"> - <div class="card-header"> - <h6 class="mb-0">Gruppen+Projekt Chat</h6> - </div> - <div class="card-body"> - <ul class="list-group"> - <li class="list-group-item"> - <div class="media"> - <div></div> - <div class="media-body"> - <div class="media" style="overflow:visible;"> - <div><img src="../libs/img/1.jpg" class="mr-3" - style="width: 25px; height:25px;"></div> - <div class="media-body" style="overflow:visible;"> - <div class="row"> - <div class="col-md-12"> - <p><a href="#">Sara Doe:</a> This guy has been going - 100+ MPH on side streets. <br> - <small class="text-muted">August 6, 2016 @ 10:35am - </small> - </p> - </div> - </div> - </div> - </div> - </div> - </div> - </li> - <li class="list-group-item"> - <div class="media"> - <div></div> - <div class="media-body"> - <div class="media" style="overflow:visible;"> - <div><img src="../libs/img/2.jpg" class="mr-3" - style="width: 25px; height:25px;"></div> - <div class="media-body" style="overflow:visible;"> - <div class="row"> - <div class="col-md-12"> - <p><a href="#">Brennan Prill:</a> This guy has been - going 100+ MPH on side streets. <br> - <small class="text-muted">August 6, 2016 @ 10:35am - </small> - </p> - </div> - </div> - </div> - </div> - </div> - </div> - </li> - </ul> - <button class="btn btn-light"> - Add Comment - </button> - </div> - </div> - </td> - </tr> - </table> - </div> - </div> - <footer:footer/> -</div> -</body> - -</html> diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-learninggoal.jsp b/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-learninggoal.jsp index 081e7b3f1f8cff3dc18ef019c02d3958e0aaeac6..76acbd7864f487810002a995584e4ea2c5ed32f3 100644 --- a/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-learninggoal.jsp +++ b/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-learninggoal.jsp @@ -1,7 +1,7 @@ -<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> <!DOCTYPE html> <html> @@ -25,62 +25,61 @@ <body> <menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <div class="container-fluid"><a class="btn btn-link" role="button" href="#menu-toggle" id="menu-toggle"></a> - <div class="row"> - <div class="col-md-12"> +<div class="page-content-wrapper"> + <div class="container-fluid"><a class="btn btn-link" role="button" href="#menu-toggle" id="menu-toggle"></a> + <div class="row"> + <div class="col-md-12"> - <div class="page-header"></div> - </div> + <div class="page-header"></div> </div> </div> </div> +</div> - <div> - <div class="container"> - <div class="row"> - <div class="col-md-offset-3 col-sm-8 col-xs-1"> - <h3>Projekt</h3> - <div class="dropdown"> - <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" - id="projectDropdown">Projekt auswählen - <span class="caret"></span></button> - <ul class="dropdown-menu" id="dropdownOptions"> - </ul> - </div> +<div> + <div class="container"> + <div class="row"> + <div class="col-md-offset-3 col-sm-8 col-xs-1"> + <h3>Projekt</h3> + <div class="dropdown"> + <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" + id="projectDropdown">Projekt auswählen + <span class="caret"></span></button> + <ul class="dropdown-menu" id="dropdownOptions"> + </ul> </div> </div> - <div id="tablesHolder"></div> - <!-- <table class="table table-striped table-bordered table-list" style="width: 40%;margin-top: 10px;"> - <thead id="tableHead"> - <tr> - <th class="hidden-xs">Student</th> - <th>E-Mail</th> - </tr> - </thead> - <tbody> - <tr id="student2"> + </div> + <div id="tablesHolder"></div> + <!-- <table class="table table-striped table-bordered table-list" style="width: 40%;margin-top: 10px;"> + <thead id="tableHead"> + <tr> + <th class="hidden-xs">Student</th> + <th>E-Mail</th> + </tr> + </thead> + <tbody> + <tr id="student2"> - </tr> - <tr id="student3"> + </tr> + <tr id="student3"> - </tr> - <tr id="student4"> + </tr> + <tr id="student4"> - </tr> - <tr id="student5"> + </tr> + <tr id="student5"> - </tr> + </tr> - </tbody> + </tbody> - </table>--> - <!--<p>Hier können sie ihre Gruppen zu allen Projekten einsehen. Klicken sie dafür auf das Dropdownmenü - um ihren Kurs auszuwählen.</p>--> - </div> + </table>--> + <!--<p>Hier können sie ihre Gruppen zu allen Projekten einsehen. Klicken sie dafür auf das Dropdownmenü + um ihren Kurs auszuwählen.</p>--> </div> </div> +<footer:footer/> </body> </html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-manual.jsp b/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-manual.jsp index d2ba9603d062c13707497484ead9d98b710a583d..07f474e97199309e427f80f6433697bc62498a9b 100644 --- a/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-manual.jsp +++ b/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-manual.jsp @@ -5,12 +5,58 @@ Time: 13:36 To change this template use File | Settings | File Templates. --%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page contentType="text/html;charset=UTF-8" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> + <html> <head> - <title>Title</title> + <omniDependencies:omniDependencies hierarchy="1"/> + <script src="js/create-groups-manual.js"></script> + <link rel="stylesheet" href="css/create-groups-manual.css"> </head> + +<script id="groupTemplate" type="text/x-jQuery-tmpl"> + <div style="display: block; margin-top: 5px; margin-left: 5px;" id="${groupName}"> + <button type="button" class="group-button list-group-item list-group-item-action">${groupName}</button> + {{each groupMember}} + <button type="button" name="student" class="student-button list-group-item list-group-item-action"> + <span>${name}</span> + <p name="userEmail" hidden>${email}</p></button> + {{/each}} + <p name="chatRoomId" hidden>${chatRoomId}</p> + </div> +</script> + <body> +<menu:menu hierarchy="1"/> +<div style="display: block"> + <div style="display: flex"> + <div style="display:block"> + <div id="studentsWithoutGroup" class="alert alert-warning"> + Es sind noch Studenten "gruppenlos". + </div> + <div id="done" class="alert alert-success"> + Gruppen wurden gespeichert. + </div> + Gruppen: + <div class="list-group" style="display: flex; flex-wrap: wrap;" id="groupsInProject"> + <div style="display:block;" id="gruppenlos"> + <button type="button" class="group-button list-group-item list-group-item-action active"> + gruppenlos + </button> + </div> + </div> + <button id="openNewGroup">neue Gruppe öffnen</button> + </div> + </div> + <div style="margin-top: 50px; margin-left: 5px;margin-right: 5px;"> + <button id="btnRelocate"><<verschieben>></button> + </div> +</div> +<button type="button" class="btn-success" id="btnSave"> speichern</button> +<footer:footer/> </body> </html> diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-preferences.jsp b/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-preferences.jsp index 674145cd5ce3d0f22f728673b41e59f798885837..84dc20c81818387c51243ebba2f26afc900e1a27 100644 --- a/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-preferences.jsp +++ b/gemeinsamforschen/src/main/webapp/groupfinding/create-groups-preferences.jsp @@ -10,8 +10,6 @@ <head> <title>Gruppenfindung basierend auf psychometrischen Merkmalen wurde noch nicht implementiert. Klicken sie auf zurück im browser</title> - <span>@Mirjam: Hier kämen deine Einstellungen für den Dozierenden bezüglich der Gruppenfindung basierend auf - Merkmalen</span> </head> <body> diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/css/create-groups-manual.css b/gemeinsamforschen/src/main/webapp/groupfinding/css/create-groups-manual.css new file mode 100644 index 0000000000000000000000000000000000000000..76ee71207ee525e8b52f04ab4b3c5ef5efd84e44 --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/groupfinding/css/create-groups-manual.css @@ -0,0 +1,8 @@ +.group-button{ + margin-left: 2px; + height: 35px; +} + +.student-button{ + margin-left: 2px; +} \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/css/edit-groups.css b/gemeinsamforschen/src/main/webapp/groupfinding/css/edit-groups.css new file mode 100644 index 0000000000000000000000000000000000000000..6729fb50b96d4f3be747404896995f7d0c80f867 --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/groupfinding/css/edit-groups.css @@ -0,0 +1,23 @@ +.sortableGroup { + list-style-type: none; + margin: 0; + float: left; + margin-right: 10px; + background: #eee; + padding: 5px; + min-width: 143px; + max-width: 243px; + margin-bottom: 20px; +} + +.sortableGroup li { + margin: 5px; + padding: 5px; + font-size: 1.2em; + min-width: 120px; +} + +th, td { + padding: 15px; + text-align: left; +} \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/edit-groups.jsp b/gemeinsamforschen/src/main/webapp/groupfinding/edit-groups.jsp new file mode 100644 index 0000000000000000000000000000000000000000..9cef5fc006c29d1df811668ab74177fa3ef75342 --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/groupfinding/edit-groups.jsp @@ -0,0 +1,54 @@ +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%-- + Created by IntelliJ IDEA. + User: dehne + Date: 05.11.2018 + Time: 13:23 + To change this template use File | Settings | File Templates. +--%> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<html> +<head> + <title>Title</title> + <omniDependencies:omniDependencies hierarchy="1"/> + <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> + <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> + <%--<link rel="stylesheet" href="/resources/demos/style.css">--%> + <link rel="stylesheet" href="css/edit-groups.css"> + <script src="js/edit-groups.js"></script> + +</head> +<body> + +<%--TODO das mit %3 oder %4 auf mehrere Zeilen verteilen--%> +<script id="groupTemplate" type="text/x-jQuery-tmpl"> + {{each(prop,group) bla}} + <div class="container-fluid"> + <h3>Gruppe ${group.id}</h3> + <ul id="group_${group.id}" class="droptrue sortableGroup"> + {{each(prop2, val) group.members }} + <li class="ui-state-default"> + <table> + <tr> + <td> ${val.name} </td> + <td> ${val.email} </td> + </tr> + </table> + </li> + {{/each}} + </ul> + </div> + {{/each}} + <div class="container-fluid"> + <h3>Gruppe ${lastGroupId}</h3> + <ul id="group_${lastGroupId}" class="droptrue sortableGroup"></ul> + </div> + + +</script> +<div id="editable_groups"></div> +<button id="persistNewGroups">Gruppen speichern</button> +<button id="finalizeNewGroups">Gruppen finalisieren</button> + +</body> +</html> diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/enter-preferences.jsp b/gemeinsamforschen/src/main/webapp/groupfinding/enter-preferences.jsp index 4d8d1e0725bb932b782adc74d8fcf3317457a8c7..39d6724d61dbd02268e2d462bd992b2ef8e61c4b 100644 --- a/gemeinsamforschen/src/main/webapp/groupfinding/enter-preferences.jsp +++ b/gemeinsamforschen/src/main/webapp/groupfinding/enter-preferences.jsp @@ -1,7 +1,9 @@ -<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> + <!DOCTYPE html> <html> @@ -19,70 +21,63 @@ <div class="sk-cube3 sk-cube"></div> </div> <menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <div class="container-fluid"><a class="btn btn-link" role="button" href="#menu-toggle" id="menu-toggle"></a> - <div class="row"> - <div class="col-md-12"> - <h3>Geben Sie hier ihre Präferenzen ein!</h3> - <div class="page-header"></div> - </div> - </div> - </div> +<h3>Geben Sie hier ihre Präferenzen ein!</h3> +<fieldset> + <legend>Passwort</legend> + <input placeholder="******" id="projectPassword"> + <div class="alert alert-danger" id="projectWrongPassword">Das Passwort ist falsch.</div> +</fieldset> +<fieldset> + <legend style="margin-left:13px;">Lernziele</legend> + <div id="competencies"> + <input class="form-control" type="text" id="competencies0" name="competencies" required="" + placeholder="Ich möchte folgendes lernen:" + style="margin:0px;max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> </div> + <button + class="btn btn-default" type="button" + style="margin-left:443px;margin-top:-88px;height:36px;width:33px;" + id="addCompetenceButton">+ + </button> + <button + class="btn btn-default" type="button" + style="margin-left:10px;margin-top:-88px;height:36px;width:33px;" + id="subtractCompetenceButton">- + </button> - <fieldset> - <legend style="margin-left:13px;">Lernziele</legend> - <div id="competencies"> - <input class="form-control" type="text" id="competencies0" name="competencies" required="" - placeholder="Ich möchte folgendes lernen:" - style="margin:0px;max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> - </div> - <button - class="btn btn-default" type="button" - style="margin-left:443px;margin-top:-88px;height:36px;width:33px;" - id="addCompetenceButton">+ - </button> - <button - class="btn btn-default" type="button" - style="margin-left:10px;margin-top:-88px;height:36px;width:33px;" - id="subtractCompetenceButton">- - </button> - - </fieldset> - <fieldset style="margin-bottom:-3px;"> - <legend style="margin-left:13px;">Forschungsfrage</legend> - <div id="researchQuestion"> - <input class="form-control" id="researchQuestion0" type="text" name="researchQuestion" required="" - placeholder="Meine Forschungsfrage(n): " - style="margin:0px;max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> - </div> - <button class="btn btn-default" type="button" - style="margin-left:443px;margin-top:-88px;height:36px;width:33px;" - id="addResearchQuestionButton">+ - </button> - <button - class="btn btn-default" type="button" - style="margin-left:10px;margin-top:-88px;height:36px;width:33px;" - id="subtractCResearchQuestionButton">- - </button> - </fieldset> - <fieldset> - <legend style="margin-left:13px;">Tags</legend> - <p class="alert alert-warning" style="width:520px;">Wähle 2 der hier angegebenen Tags aus, die am ehesten zu - deiner Forschungsfrage passen.</p> - <div id="tags"> - - </div> - </fieldset> - <button class="btn btn-primary" id="studentFormSubmit" style="width:90px;margin-left:169px;margin-top:13px;"> - eintragen +</fieldset> +<fieldset style="margin-bottom:-3px;"> + <legend style="margin-left:13px;">Forschungsfrage</legend> + <div id="researchQuestion"> + <input class="form-control" id="researchQuestion0" type="text" name="researchQuestion" required="" + placeholder="Meine Forschungsfrage(n): " + style="margin:0px;max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> + </div> + <button class="btn btn-default" type="button" + style="margin-left:443px;margin-top:-88px;height:36px;width:33px;" + id="addResearchQuestionButton">+ + </button> + <button + class="btn btn-default" type="button" + style="margin-left:10px;margin-top:-88px;height:36px;width:33px;" + id="subtractCResearchQuestionButton">- </button> - <div class="alert alert-warning" style="width:520px" role="alert"> - Das Verarbeiten der Lernziele und das Gruppenmatching kann einen Moment dauern! +</fieldset> +<fieldset> + <legend style="margin-left:13px;">Tags</legend> + <p class="alert alert-warning" style="width:520px;">Wähle 2 der hier angegebenen Tags aus, die am ehesten zu + deiner Forschungsfrage passen.</p> + <div id="tags"> + </div> +</fieldset> +<button class="btn btn-primary" id="studentFormSubmit" style="width:90px;margin-left:169px;margin-top:13px;"> + eintragen +</button> +<div class="alert alert-warning" style="width:520px" role="alert"> + Das Verarbeiten der Lernziele und das Gruppenmatching kann einen Moment dauern! </div> - +<footer:footer/> </body> diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/js/config.js b/gemeinsamforschen/src/main/webapp/groupfinding/js/config.js index af79dfc9d5b448c751e0f1bf6ef47fc2b0e569b8..f52b55460d8f5c94ec07e540cb59537a1f188dad 100644 --- a/gemeinsamforschen/src/main/webapp/groupfinding/js/config.js +++ b/gemeinsamforschen/src/main/webapp/groupfinding/js/config.js @@ -1,3 +1,3 @@ //var compbaseUrl = "https://esb.uni-potsdam.de:8243/services/competenceBase"; //var compbaseUrl = "http://fleckenroller.cs.uni-potsdam.de/app/competence-database-prod"; -var compbaseUrl = "https://apiup.uni-potsdam.de/endpoints/competenceAPI"; +let compbaseUrl = "https://apiup.uni-potsdam.de/endpoints/competenceAPI"; diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/js/create-groups-manual.js b/gemeinsamforschen/src/main/webapp/groupfinding/js/create-groups-manual.js new file mode 100644 index 0000000000000000000000000000000000000000..8ff88d07d104d341ac40ca35da0be6aede621e5a --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/groupfinding/js/create-groups-manual.js @@ -0,0 +1,159 @@ +$(document).ready(function () { + $('#studentsWithoutGroup').hide(); + $('#done').hide(); + getAllGroups(function (allGroups) { + groupsToTemplate(allGroups, function (done) { + selectableButtons(done); + }); + }); + $('#btnRelocate').click(function () { + relocateMember(function (done) { + selectableButtons(done); + }); + relocateMember(function (done) { + selectableButtons(done); //i have no clue why this needs to be called twice, but it seems necessary + }); + }); + $('#btnSave').click(function () { + viewToGroup(function (groups) { + saveNewGroups(groups); + }) + }); + $('#openNewGroup').click(function () { + openNewGroup(function(done){ + selectableButtons(done); + }); + selectableButtons(true); //i have no clue why this needs to be called twice, but it seems necessary + }); +}); + +function getAllGroups(callback) { + $.ajax({ + url: "../rest/group/all/projects/" + $('#projectName').html().trim(), + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache" + }, + type: 'GET', + success: function (response) { + callback(response.groups); + }, + error: function (a) { + } + }); +} + +function groupsToTemplate(allGroups, callback) { + let groupTmplObject = []; + for (let group = 0; group < allGroups.length; group++) { + groupTmplObject.push({ + groupName: "group" + group, + groupMember: allGroups[group].members, + chatRoomId: allGroups[group].chatRoomId, + }); + } + $('#groupTemplate').tmpl(groupTmplObject).appendTo('#groupsInProject'); + let done = true; + callback(done); +} + +function selectableButtons(done) { + if (done) { + $(".student-button").click(function () { + $(this).toggleClass('active'); + }); + $(".group-button").click(function () { + $(".group-button.active").toggleClass('active'); + $(this).toggleClass('active'); + }); + } +} + +function relocateMember(callback) { + let memberBtns = $('.student-button.active'); + let newGroupBtn = $('.group-button.active'); + newGroupBtn.each(function () { + let newGroup = $('#' + $(this).html().trim()); + memberBtns.each(function () { + $(this).toggleClass('active'); + $(newGroup).append($(this)); + }); + }); + let done = true; + callback(done); +} + +function viewToGroup(callback) { + if ($('#gruppenlos').children().length > 1) { + $('#studentsWithoutGroup').show(); + return null; + } + let groups = []; + $('#groupsInProject').children().each(function () { + if ($(this).attr("id").trim() !== "gruppenlos") { + let members = []; + let chatRoomId = 0; + $(this).children().each(function () { + if ($(this).attr("name") === "student") { + let entries = $(this).children(); + let email = entries[1].innerText; + let name = entries[0].innerText; + members.push({ + email: email, + name: name, + rocketChatPersonalAccessToken: "", + rocketChatUserId: "", + rocketChatUsername: "", + student: true + }); + } + if ($(this).attr("name") === "chatRoomId") { + chatRoomId = $(this).html().trim(); + } + }); + if (members.length !== 0){ + groups.push({ + chatRoomId: chatRoomId, + id: "", + members: members, + }) + } + } + }); + callback(groups); +} + +function saveNewGroups(groups) { + let data = JSON.stringify(groups); + $.ajax({ + url: "../rest/group/projects/" + $('#projectName').html().trim(), + data: data, + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache" + }, + type: 'POST', + success: function (response) { + $('#done').show(); + setTimeout(function(){ + document.location.href="../project/tasks-docent.jsp?projectName="+$('#projectName').html().trim(); + }, 1000); + }, + error: function (a) { + alert(a); + } + }); +} + +function openNewGroup(callback) { + let groupTmplObject = []; + let nextGroup = $('#groupsInProject').children().length; + groupTmplObject.push({ + groupName: "group" + nextGroup, + groupMember: [], + chatRoomId: "", + }); + $('#groupTemplate').tmpl(groupTmplObject).appendTo('#groupsInProject'); + let done = true; + callback(done); +} \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/js/create-preferences.js b/gemeinsamforschen/src/main/webapp/groupfinding/js/create-preferences.js index f1d00c293522230357aac623e7eee24933a0e667..fc250d0da2a572c7daad48a6c2c5d5faf9ae4ed4 100644 --- a/gemeinsamforschen/src/main/webapp/groupfinding/js/create-preferences.js +++ b/gemeinsamforschen/src/main/webapp/groupfinding/js/create-preferences.js @@ -3,6 +3,7 @@ */ $(document).ready(function () { + $('#projectWrongPassword').hide(); printTags(); $("#competencies0").focus(); $("#studentFormSubmit").on("click", function () { @@ -25,9 +26,9 @@ $(document).ready(function () { function addInput(name) { //creates a new input-Field with the ID 'nameX' where X is number of elements with 'name' as ID - var i = document.getElementsByName(name).length; + let i = document.getElementsByName(name).length; let div = document.getElementById(name); - var newInput = document.createElement("span"); + let newInput = document.createElement("span"); newInput.innerHTML = "<input class='form-control' " + "type='text' " + "name='" + name + "' " + @@ -37,9 +38,9 @@ function addInput(name) { //creates a new input-Field with the ID 'nameX' } function deletInput(name) { //deletes latest input-Field with the ID 'nameX' where X is number of elements with 'name' as ID - var i = document.getElementsByName(name).length; + let i = document.getElementsByName(name).length; if (i > 1) { - var lastEntry = document.getElementById(name + "" + (i - 1)); + let lastEntry = document.getElementById(name + "" + (i - 1)); lastEntry.parentNode.removeChild(lastEntry); } } @@ -48,21 +49,21 @@ function deletInput(name) { //deletes latest input-Field with the ID 'nam * selects the tags from the db and prints the seleciton */ function printTags() { - var url = "../../gemeinsamforschen/rest/project/tags/" + getProjectName(); + let url = "../../gemeinsamforschen/rest/project/tags/" + getProjectName(); $.ajax({ url: url, Accept: "application/json", contentType: "text/plain", success: function (response) { - var tagList = response; + let tagList = response; for (i = 0; i < tagList.length; i++) { - var newInput = document.createElement("label"); + let newInput = document.createElement("label"); newInput.innerHTML = "<div class='checkbox checkbox-primary' >" + "<input id='tag" + i + "' " + " class='styled' " + "name='tag'" + "type='checkbox' " + ">" + "<label for='tag" + i + "' " + ">" + tagList[i] + "</label>" + "</div>"; - var div = document.getElementById('tags'); + let div = document.getElementById('tags'); div.appendChild(newInput); } }, @@ -77,18 +78,17 @@ function printTags() { function takesPartInProject(context) { document.getElementById('loader').className = "loader"; - document.getElementById('wrapper').className = "wrapper-inactive"; - var allTheTags = []; - var allTheCompetencies = []; - var allTheResearchQuestions = []; - for (i = 0; i < document.getElementsByName("competencies").length; i++) { //goes through all competencies and adds them to allTheCompetencies + let allTheTags = []; + let allTheCompetencies = []; + let allTheResearchQuestions = []; + for (let i = 0; i < document.getElementsByName("competencies").length; i++) { //goes through all competencies and adds them to allTheCompetencies allTheCompetencies.push(document.getElementsByName("competencies")[i].value); } - for (i = 0; i < document.getElementsByName("researchQuestions").length; i++) { //goes through all competencies and adds them to allTheResearchQuestions + for (let i = 0; i < document.getElementsByName("researchQuestions").length; i++) { //goes through all competencies and adds them to allTheResearchQuestions allTheResearchQuestions.push(document.getElementsByName("researchQuestions")[i].value); } - for (i = 0; i < document.getElementsByName("tag").length; i++) { //goes through all tags and adds them to allTheTags + for (let i = 0; i < document.getElementsByName("tag").length; i++) { //goes through all tags and adds them to allTheTags if (document.getElementById("tag" + i).checked) { allTheTags.push(document.getElementById("tag" + i).value); } @@ -100,7 +100,6 @@ function takesPartInProject(context) { $(".alert").css('background-color', 'lightcoral'); allTheTags = []; document.getElementById('loader').className = "loader-inactive"; - document.getElementById('wrapper').className = "wrapper"; return false; } if (allTheTags.length < 2) { @@ -108,20 +107,20 @@ function takesPartInProject(context) { $(".alert").css('background-color', 'lightcoral'); allTheTags = []; document.getElementById('loader').className = "loader-inactive"; - document.getElementById('wrapper').className = "wrapper"; return false; } - var data = { //JSON object 'data' collects everything to send + let data = { //JSON object 'data' collects everything to send "competences": allTheCompetencies, "researchQuestions": allTheResearchQuestions, "tagsSelected": allTheTags }; - var userEmail = getUserEmail(); - var projectName = getProjectName(); - var dataString = JSON.stringify(data); //to send correctly, data needs to be stringified - var url = compbaseUrl + "/api2/user/" + userEmail + "/projects/" + projectName + "/preferences"; + let userEmail = getUserEmail(); + let projectName = getProjectName(); + loginProject(projectName); + let dataString = JSON.stringify(data); //to send correctly, data needs to be stringified + let url = compbaseUrl + "/api2/user/" + userEmail + "/projects/" + projectName + "/preferences"; $.ajax({ url: url, type: 'PUT', @@ -131,11 +130,33 @@ function takesPartInProject(context) { success: function (response) { console.log(response); document.getElementById('loader').className = "loader-inactive"; - document.getElementById('wrapper').className = "wrapper"; - location.href = "../project/overview-student.jsp"; + location.href = "../project/myCourses-student.jsp"; }, error: function (a, b, c) { console.log(a); } }); +} + +function loginProject(projectName) { + let password = $('#projectPassword').val(); + let url = "../../gemeinsamforschen/rest/project/login/"+projectName+"?password="+password; + if (projectName === "") { + return false; + } else { + $.ajax({ + url: url, + projectName: projectName, + Accept: "text/plain; charset=utf-8", + contentType: "text/plain", + success: function (response) { + if (response === "wrong password") { //if response !== project missing and not wrong password, its the projectName + $('#projectWrongPassword').show(); + } + }, + error: function (a) { + console.log(a); + } + }); + } } \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/groupfinding/js/edit-groups.js b/gemeinsamforschen/src/main/webapp/groupfinding/js/edit-groups.js new file mode 100644 index 0000000000000000000000000000000000000000..94c7aa757ca1bc1b79cdd642b5b9999b061e3241 --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/groupfinding/js/edit-groups.js @@ -0,0 +1,63 @@ +var projectName = getProjectName(); + +$(function () { + + serverSide({ + hierarchyLevel:1, + modulePath:"/group", + methodPath: "/projects/?/groups", + pathParams: [projectName], + queryParams: [] + },"GET", printGroups); + + $(".sortableGroup").disableSelection(); + + $("#persistNewGroups").click(function () { + + serverSide({ + hierarchyLevel: 1, + modulePath: "/group", + methodPath: "/projects/?/groups", + pathParams: [projectName], + queryParams: [], + entity: getGroupDataFromHtml() + }, "PUT", function (a,b,c) { + console.log(a); + }) + }); + $("#finalizeNewGroups").click(function () { + // TODO get firstgroup + }); + + function getGroupDataFromHtml() { + var i = 1; + //$("#group_1 > li > table > tbody > tr > td:nth-child(2)") + } + + function printGroups(a,b,c) { + var firstgroup = + groups = { + bla: [ + { + id: "1", + members: [{name:"me", email:"egal@stuff.com"}, {name:"him", email:"egal@stuff.com"}, {name:"her", email:"egal@stuff.com"}] + }, + { + id: "2", + members: [{name:"me2", email:"egal@stuff.com"}, {name:"him2", email:"egal@stuff.com"}, {name:"her2", email:"egal@stuff.com"}] + } + ], + lastGroupId: "44" + }; + + // create a group + $("#groupTemplate") + .tmpl(firstgroup) + .appendTo("#editable_groups"); + + $("ul.droptrue").sortable({ + connectWith: "ul" + }); + + } +}); diff --git a/gemeinsamforschen/src/main/webapp/index.jsp b/gemeinsamforschen/src/main/webapp/index.jsp index 106ee11fefa8b5cd15602d2959dae85c1caf36e2..a944f58460cb0219ee83b822de0543b634c9c48d 100644 --- a/gemeinsamforschen/src/main/webapp/index.jsp +++ b/gemeinsamforschen/src/main/webapp/index.jsp @@ -19,7 +19,7 @@ <body> <div class="login-clean"> - <form method="post" action="./rest/user/exists"> + <form method="post" action="rest/user/exists"> <h2 class="sr-only">Login Formular</h2> <div class="illustration"><img src="libs/img/fides-logo.svg"></div> <div class="form-group"><input class="form-control" type="email" name="email" placeholder="Email" autofocus> diff --git a/gemeinsamforschen/src/main/webapp/journal/create-journal.jsp b/gemeinsamforschen/src/main/webapp/journal/create-journal.jsp index 0546575e0bfd3649cdb5d8d883c2501f0c84eff4..fcb7854250eeed3430cba9b9ce4b7f8c2513dc63 100644 --- a/gemeinsamforschen/src/main/webapp/journal/create-journal.jsp +++ b/gemeinsamforschen/src/main/webapp/journal/create-journal.jsp @@ -1,5 +1,7 @@ <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> <!DOCTYPE html> <html> @@ -15,77 +17,66 @@ <body> <menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <div class="container-fluid"> - <h1 id="projectName">project1 - <a href="#"> - <span class="glyphicon glyphicon-envelope" - style="font-size:27px;margin-top:-17px;margin-left:600px;"></span> - </a> - <a href="#"> - <span class="glyphicon glyphicon-cog" - style="font-size:29px;margin-left:5px;margin-top:-25px;"></span> - </a></h1> - </div> - <div> - <table> - <tr> - <td id="yourContent"> - <h1> Tagebucheintrag erstellen </h1> - - <form id="journalform" class="form-journal" method="POST" action="../rest/journal/save"> - - <input type="hidden" id="student" name="student"> - <input type="hidden" id="project" name="project"> - <input type="hidden" id="journalid" name="id"> - - <div class="journal-form-container"> - - <div class="journal-form-visibility"> - Sichtbarkeit: - <select id="visibility" name="visibility" form="journalform"> - <option value="ALL"> Alle</option> - <option value="GROUP"> Gruppe</option> - <option value="DOZENT"> Dozent</option> - <option value="NONE"> Nur Ich</option> - </select> - </div> - - <div class="journal-form-category"> - Kategorie: - <select name="category" form="journalform"> - <option value="TITEL"> Titel</option> - <option value="RECHERCHE"> Recherche</option> - <option value="LITERATURVERZEICHNIS"> Literaturverzeichnis</option> - <option value="FORSCHUNGSFRAGE"> Forschungsfrage</option> - <option value="UNTERSUCHUNGSKONZEPT"> Untersuchungskonzept</option> - <option value="METHODIK"> Methodik</option> - <option value="DURCHFUEHRUNG"> Durchführung</option> - <option value="AUSWERTUNG"> Auswertung</option> - - </select> - </div> - - - <div class="journal-form-editor"> - <textarea id="editor" name="text" form="journalform"></textarea> - </div> - - <div class="journal-form-buttons"> - <input class="btn btn-default btn-sm" type="submit"> - <a id="backLink" class="btn btn-default btn-sm"> Zurück </a> - </div> +<div class="page-content-wrapper"> + <headLine:headLine/> + <div> + <table> + <tr> + <td id="yourContent"> + <h1> Tagebucheintrag erstellen </h1> + + <form id="journalform" class="form-journal" method="POST" action="../rest/journal/save"> + + <input type="hidden" id="student" name="student"> + <input type="hidden" id="project" name="project"> + <input type="hidden" id="journalid" name="id"> + + <div class="journal-form-container"> + + <div class="journal-form-visibility"> + Sichtbarkeit: + <select id="visibility" name="visibility" form="journalform"> + <option value="ALL"> Alle</option> + <option value="GROUP"> Gruppe</option> + <option value="DOZENT"> Dozent</option> + <option value="NONE"> Nur Ich</option> + </select> + </div> + + <div class="journal-form-category"> + Kategorie: + <select name="category" form="journalform"> + <option value="TITEL"> Titel</option> + <option value="RECHERCHE"> Recherche</option> + <option value="LITERATURVERZEICHNIS"> Literaturverzeichnis</option> + <option value="FORSCHUNGSFRAGE"> Forschungsfrage</option> + <option value="UNTERSUCHUNGSKONZEPT"> Untersuchungskonzept</option> + <option value="METHODIK"> Methodik</option> + <option value="DURCHFUEHRUNG"> Durchführung</option> + <option value="AUSWERTUNG"> Auswertung</option> + + </select> + </div> + + <div class="journal-form-editor"> + <textarea id="editor" name="text" form="journalform"></textarea> </div> - </form> - </td> - </tr> - </table> - </div> + <div class="journal-form-buttons"> + <input class="btn btn-default btn-sm" type="submit"> + <a id="backLink" class="btn btn-default btn-sm"> Zurück </a> + </div> + + </div> + </form> + + </td> + </tr> + </table> </div> </div> +<footer:footer/> </body> </html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/journal/edit-description.jsp b/gemeinsamforschen/src/main/webapp/journal/edit-description.jsp index 4cabd404157e6604010061a91bcb66086c6bc2b8..d5e65bbd5a0508220ecd733fbc0a5babd4f30d66 100644 --- a/gemeinsamforschen/src/main/webapp/journal/edit-description.jsp +++ b/gemeinsamforschen/src/main/webapp/journal/edit-description.jsp @@ -1,5 +1,7 @@ <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> <!DOCTYPE html> <html> @@ -12,7 +14,6 @@ <body> <menu:menu hierarchy="1"/> -<div id="wrapper"> <h1> Projektbeschreibung bearbeiten </h1> <form id="descriptionform" class="form-journal" method="POST" @@ -20,7 +21,7 @@ <textarea id="editor" name="text" form="descriptionform"></textarea> </form> -</div> +<footer:footer/> </body> </html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/journal/eportfolio.jsp b/gemeinsamforschen/src/main/webapp/journal/eportfolio.jsp index f9c41e5f7aabb61d31dc5118b6051253a9930831..d9bc65929d505aae24d33313bba61a5031e9e0b8 100644 --- a/gemeinsamforschen/src/main/webapp/journal/eportfolio.jsp +++ b/gemeinsamforschen/src/main/webapp/journal/eportfolio.jsp @@ -1,5 +1,7 @@ <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> <!DOCTYPE html> <html> @@ -14,76 +16,68 @@ <body> <menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <div class="container-fluid"> - <h1 id="projectName">project1 - <a href="#"> - <span class="glyphicon glyphicon-envelope" - style="font-size:27px;margin-top:-17px;margin-left:600px;"></span> - </a> - <a href="#"> - <span class="glyphicon glyphicon-cog" style="font-size:29px;margin-left:5px;margin-top:-25px;"></span> - </a></h1> - </div> - <div> - <table> - <tr> - <td id="yourContent"> - - <h1>E-Portfolio</h1> - - <ul class="nav nav-tabs"> - <li class="active"><a data-toggle="tab" href="#description">Beschreibung</a></li> - <li><a data-toggle="tab" href="#journal-container">Lerntagebuch</a></li> - </ul> - <div class="tab-content"> - - <div id = "description" class="tab-pane fade in active "> - <div class="journal-description-container"> - <div class="journal-description-title"> - </div> - <div class="journal-description-edit" id="description-edit" align="right"> - <a id="editDescriptionLink" class="btn btn-default btn-sm"> - <i class="fa fa-pencil-alt"></i> Bearbeiten</a> - <a class="btn btn-default btn-sm" data-toggle="modal" data-target="#closeDescriptionModal"><i class="fa fa-check-square" aria-hidden="true"></i>Abschließen</a> - <!-- TODO: Variabel --> - <div class="exportLink"> - </div> - - </div> - <div class="journal-description-text"> - </div> - <div class="journal-description-group"> - <h3>Gruppe</h3> +<div class="page-content-wrapper"> + <headLine:headLine/> + <div> + <table> + <tr> + <td id="yourContent"> + + <h1>E-Portfolio</h1> + + <ul class="nav nav-tabs"> + <li class="active"><a data-toggle="tab" href="#description">Beschreibung</a></li> + <li><a data-toggle="tab" href="#journal-container">Lerntagebuch</a></li> + </ul> + <div class="tab-content"> + + <div id="description" class="tab-pane fade in active "> + <div class="journal-description-container"> + <div class="journal-description-title"> + </div> + <div class="journal-description-edit" id="description-edit" align="right"> + <a id="editDescriptionLink" class="btn btn-default btn-sm"> + <i class="fa fa-pencil-alt"></i> Bearbeiten</a> + <a class="btn btn-default btn-sm" data-toggle="modal" + data-target="#closeDescriptionModal"><i class="fa fa-check-square" + aria-hidden="true"></i>Abschließen</a> + <!-- TODO: Variabel --> + <div class="exportLink"> </div> - <div class="journal-description-links"> - <h3>Links</h3> - </div> + </div> + <div class="journal-description-text"> + </div> + <div class="journal-description-group"> + <h3>Gruppe</h3> + </div> + <div class="journal-description-links"> + <h3>Links</h3> + + </div> - </div> </div> + </div> - <div id="journal-container" class="tab-pane fade"> - <h2>Lerntagebuch</h2> - <div class="input-group"> - <select id="journalfilter" class="form-control" style="width:auto;" onchange="filterJournals()"> - <option value="ALL">Alle</option> - <option value="OWN">Eigene</option> - </select> + <div id="journal-container" class="tab-pane fade"> + <h2>Lerntagebuch</h2> + <div class="input-group"> + <select id="journalfilter" class="form-control" style="width:auto;" + onchange="filterJournals()"> + <option value="ALL">Alle</option> + <option value="OWN">Eigene</option> + </select> - <a id="createJournalLink"class="btn btn-default btn-sm" >Neu</a> - </div> - <div class="journal"> - </div> - </div> + <a id="createJournalLink" class="btn btn-default btn-sm">Neu</a> + </div> + <div class="journal"> + </div> </div> - </td> - </tr> - </table> - </div> + </div> + </td> + </tr> + </table> </div> </div> @@ -95,7 +89,7 @@ <h4 class="modal-title">Link hinzufügen</h4> </div> <div class="modal-body"> - <form id="linkform" method="POST" action="../rest/projectdescription/addLink" > + <form id="linkform" method="POST" action="../rest/projectdescription/addLink"> <input type="hidden" name="projectdescriptionId" id="projectdescriptionId" value=""/> Name:<br> <input type="text" name="name" form="linkform"> @@ -103,7 +97,7 @@ URL:<br> <input type="url" name="link" form="linkform"> <br><br> - <input class="btn btn-default" type="submit" > + <input class="btn btn-default" type="submit"> <button type="button" class="btn btn-default" data-dismiss="modal">Abbrechen</button> </form> </div> @@ -158,6 +152,7 @@ </div> </div> </div> +<footer:footer/> </body> </html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/journal/js/create-journal.js b/gemeinsamforschen/src/main/webapp/journal/js/create-journal.js index 1b68406c89e69f086a1e95803bfd593e9acfcf96..4bc23f2ec6efacb33dc5d4e227214a4d05579539 100644 --- a/gemeinsamforschen/src/main/webapp/journal/js/create-journal.js +++ b/gemeinsamforschen/src/main/webapp/journal/js/create-journal.js @@ -1,10 +1,8 @@ -var student = getQueryVariable("token"); -var project = getQueryVariable("projectName"); - - $(document).ready(function () { - $('#student').val(student); - $('#project').val(project); + let projectName = $('#projectName').html().trim(); + let userEmail = $('#userEmail').html().trim(); + $('#student').val(projectName); + $('#project').val(userEmail); $('#backLink').on('click', function () { location.href = "eportfolio.jsp"; @@ -25,4 +23,4 @@ $(document).ready(function () { console.log(data); }); -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/libs/img/givefeedback.png b/gemeinsamforschen/src/main/webapp/libs/img/givefeedback.png new file mode 100644 index 0000000000000000000000000000000000000000..00ab3d98860663b1faf192980f9e951c82b93894 Binary files /dev/null and b/gemeinsamforschen/src/main/webapp/libs/img/givefeedback.png differ diff --git a/gemeinsamforschen/src/main/webapp/libs/img/projekt_uebersicht.png b/gemeinsamforschen/src/main/webapp/libs/img/projekt_uebersicht.png new file mode 100644 index 0000000000000000000000000000000000000000..a5bde7a165f83478a30ba17a32eb309aa8b94347 Binary files /dev/null and b/gemeinsamforschen/src/main/webapp/libs/img/projekt_uebersicht.png differ diff --git a/gemeinsamforschen/src/main/webapp/libs/img/studenten_uebersicht.png b/gemeinsamforschen/src/main/webapp/libs/img/studenten_uebersicht.png new file mode 100644 index 0000000000000000000000000000000000000000..e536468b3e278aedec0713ed054e082d0a3dd2fc Binary files /dev/null and b/gemeinsamforschen/src/main/webapp/libs/img/studenten_uebersicht.png differ diff --git a/gemeinsamforschen/src/main/webapp/libs/img/viewfeedback_recieved.png b/gemeinsamforschen/src/main/webapp/libs/img/viewfeedback_recieved.png new file mode 100644 index 0000000000000000000000000000000000000000..426a4c0002befb86b499dc389a64be80fb864d9e Binary files /dev/null and b/gemeinsamforschen/src/main/webapp/libs/img/viewfeedback_recieved.png differ diff --git a/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryContextMenu.js b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryContextMenu.js new file mode 100644 index 0000000000000000000000000000000000000000..dcea2c12fc033fab95374f71d70615cf84feac13 --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryContextMenu.js @@ -0,0 +1,2122 @@ +/** + * jQuery contextMenu v2.7.0 - Plugin for simple contextMenu handling + * + * Version: v2.7.0 + * + * Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF) + * Web: http://swisnl.github.io/jQuery-contextMenu/ + * + * Copyright (c) 2011-2018 SWIS BV and contributors + * + * Licensed under + * MIT License http://www.opensource.org/licenses/mit-license + * + * Date: 2018-10-02T14:29:27.777Z + */ + +// jscs:disable +/* jshint ignore:start */ +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node / CommonJS + factory(require('jquery')); + } else { + // Browser globals. + factory(jQuery); + } +})(function ($) { + + 'use strict'; + + // TODO: - + // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio + // create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative + + // determine html5 compatibility + $.support.htmlMenuitem = ('HTMLMenuItemElement' in window); + $.support.htmlCommand = ('HTMLCommandElement' in window); + $.support.eventSelectstart = ('onselectstart' in document.documentElement); + /* // should the need arise, test for css user-select + $.support.cssUserSelect = (function(){ + var t = false, + e = document.createElement('div'); + + $.each('Moz|Webkit|Khtml|O|ms|Icab|'.split('|'), function(i, prefix) { + var propCC = prefix + (prefix ? 'U' : 'u') + 'serSelect', + prop = (prefix ? ('-' + prefix.toLowerCase() + '-') : '') + 'user-select'; + + e.style.cssText = prop + ': text;'; + if (e.style[propCC] == 'text') { + t = true; + return false; + } + + return true; + }); + + return t; + })(); + */ + + + if (!$.ui || !$.widget) { + // duck punch $.cleanData like jQueryUI does to get that remove event + $.cleanData = (function (orig) { + return function (elems) { + var events, elem, i; + for (i = 0; elems[i] != null; i++) { + elem = elems[i]; + try { + // Only trigger remove when necessary to save time + events = $._data(elem, 'events'); + if (events && events.remove) { + $(elem).triggerHandler('remove'); + } + + // Http://bugs.jquery.com/ticket/8235 + } catch (e) { + } + } + orig(elems); + }; + })($.cleanData); + } + /* jshint ignore:end */ + // jscs:enable + + var // currently active contextMenu trigger + $currentTrigger = null, + // is contextMenu initialized with at least one menu? + initialized = false, + // window handle + $win = $(window), + // number of registered menus + counter = 0, + // mapping selector to namespace + namespaces = {}, + // mapping namespace to options + menus = {}, + // custom command type handlers + types = {}, + // default values + defaults = { + // selector of contextMenu trigger + selector: null, + // where to append the menu to + appendTo: null, + // method to trigger context menu ["right", "left", "hover"] + trigger: 'right', + // hide menu when mouse leaves trigger / menu elements + autoHide: false, + // ms to wait before showing a hover-triggered context menu + delay: 200, + // flag denoting if a second trigger should simply move (true) or rebuild (false) an open menu + // as long as the trigger happened on one of the trigger-element's child nodes + reposition: true, + // Flag denoting if a second trigger should close the menu, as long as + // the trigger happened on one of the trigger-element's child nodes. + // This overrides the reposition option. + hideOnSecondTrigger: false, + + //ability to select submenu + selectableSubMenu: false, + + // Default classname configuration to be able avoid conflicts in frameworks + classNames: { + hover: 'context-menu-hover', // Item hover + disabled: 'context-menu-disabled', // Item disabled + visible: 'context-menu-visible', // Item visible + notSelectable: 'context-menu-not-selectable', // Item not selectable + + icon: 'context-menu-icon', + iconEdit: 'context-menu-icon-edit', + iconCut: 'context-menu-icon-cut', + iconCopy: 'context-menu-icon-copy', + iconPaste: 'context-menu-icon-paste', + iconDelete: 'context-menu-icon-delete', + iconAdd: 'context-menu-icon-add', + iconQuit: 'context-menu-icon-quit', + iconLoadingClass: 'context-menu-icon-loading' + }, + + // determine position to show menu at + determinePosition: function ($menu) { + // position to the lower middle of the trigger element + if ($.ui && $.ui.position) { + // .position() is provided as a jQuery UI utility + // (...and it won't work on hidden elements) + $menu.css('display', 'block').position({ + my: 'center top', + at: 'center bottom', + of: this, + offset: '0 5', + collision: 'fit' + }).css('display', 'none'); + } else { + // determine contextMenu position + var offset = this.offset(); + offset.top += this.outerHeight(); + offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2; + $menu.css(offset); + } + }, + // position menu + position: function (opt, x, y) { + var offset; + // determine contextMenu position + if (!x && !y) { + opt.determinePosition.call(this, opt.$menu); + return; + } else if (x === 'maintain' && y === 'maintain') { + // x and y must not be changed (after re-show on command click) + offset = opt.$menu.position(); + } else { + // x and y are given (by mouse event) + var offsetParentOffset = opt.$menu.offsetParent().offset(); + offset = {top: y - offsetParentOffset.top, left: x -offsetParentOffset.left}; + } + + // correct offset if viewport demands it + var bottom = $win.scrollTop() + $win.height(), + right = $win.scrollLeft() + $win.width(), + height = opt.$menu.outerHeight(), + width = opt.$menu.outerWidth(); + + if (offset.top + height > bottom) { + offset.top -= height; + } + + if (offset.top < 0) { + offset.top = 0; + } + + if (offset.left + width > right) { + offset.left -= width; + } + + if (offset.left < 0) { + offset.left = 0; + } + + opt.$menu.css(offset); + }, + // position the sub-menu + positionSubmenu: function ($menu) { + if (typeof $menu === 'undefined') { + // When user hovers over item (which has sub items) handle.focusItem will call this. + // but the submenu does not exist yet if opt.items is a promise. just return, will + // call positionSubmenu after promise is completed. + return; + } + if ($.ui && $.ui.position) { + // .position() is provided as a jQuery UI utility + // (...and it won't work on hidden elements) + $menu.css('display', 'block').position({ + my: 'left top-5', + at: 'right top', + of: this, + collision: 'flipfit fit' + }).css('display', ''); + } else { + // determine contextMenu position + var offset = { + top: -9, + left: this.outerWidth() - 5 + }; + $menu.css(offset); + } + }, + // offset to add to zIndex + zIndex: 1, + // show hide animation settings + animation: { + duration: 50, + show: 'slideDown', + hide: 'slideUp' + }, + // events + events: { + show: $.noop, + hide: $.noop, + activated: $.noop + }, + // default callback + callback: null, + // list of contextMenu items + items: {} + }, + // mouse position for hover activation + hoveract = { + timer: null, + pageX: null, + pageY: null + }, + // determine zIndex + zindex = function ($t) { + var zin = 0, + $tt = $t; + + while (true) { + zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0); + $tt = $tt.parent(); + if (!$tt || !$tt.length || 'html body'.indexOf($tt.prop('nodeName').toLowerCase()) > -1) { + break; + } + } + return zin; + }, + // event handlers + handle = { + // abort anything + abortevent: function (e) { + e.preventDefault(); + e.stopImmediatePropagation(); + }, + // contextmenu show dispatcher + contextmenu: function (e) { + var $this = $(this); + + // disable actual context-menu if we are using the right mouse button as the trigger + if (e.data.trigger === 'right') { + e.preventDefault(); + e.stopImmediatePropagation(); + } + + // abort native-triggered events unless we're triggering on right click + if ((e.data.trigger !== 'right' && e.data.trigger !== 'demand') && e.originalEvent) { + return; + } + + // Let the current contextmenu decide if it should show or not based on its own trigger settings + if (typeof e.mouseButton !== 'undefined' && e.data) { + if (!(e.data.trigger === 'left' && e.mouseButton === 0) && !(e.data.trigger === 'right' && e.mouseButton === 2)) { + // Mouse click is not valid. + return; + } + } + + // abort event if menu is visible for this trigger + if ($this.hasClass('context-menu-active')) { + return; + } + + if (!$this.hasClass('context-menu-disabled')) { + // theoretically need to fire a show event at <menu> + // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus + // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this }); + // e.data.$menu.trigger(evt); + + $currentTrigger = $this; + if (e.data.build) { + var built = e.data.build($currentTrigger, e); + // abort if build() returned false + if (built === false) { + return; + } + + // dynamically build menu on invocation + e.data = $.extend(true, {}, defaults, e.data, built || {}); + + // abort if there are no items to display + if (!e.data.items || $.isEmptyObject(e.data.items)) { + // Note: jQuery captures and ignores errors from event handlers + if (window.console) { + (console.error || console.log).call(console, 'No items specified to show in contextMenu'); + } + + throw new Error('No Items specified'); + } + + // backreference for custom command type creation + e.data.$trigger = $currentTrigger; + + op.create(e.data); + } + op.show.call($this, e.data, e.pageX, e.pageY); + } + }, + // contextMenu left-click trigger + click: function (e) { + e.preventDefault(); + e.stopImmediatePropagation(); + $(this).trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY})); + }, + // contextMenu right-click trigger + mousedown: function (e) { + // register mouse down + var $this = $(this); + + // hide any previous menus + if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) { + $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide'); + } + + // activate on right click + if (e.button === 2) { + $currentTrigger = $this.data('contextMenuActive', true); + } + }, + // contextMenu right-click trigger + mouseup: function (e) { + // show menu + var $this = $(this); + if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) { + e.preventDefault(); + e.stopImmediatePropagation(); + $currentTrigger = $this; + $this.trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY})); + } + + $this.removeData('contextMenuActive'); + }, + // contextMenu hover trigger + mouseenter: function (e) { + var $this = $(this), + $related = $(e.relatedTarget), + $document = $(document); + + // abort if we're coming from a menu + if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { + return; + } + + // abort if a menu is shown + if ($currentTrigger && $currentTrigger.length) { + return; + } + + hoveract.pageX = e.pageX; + hoveract.pageY = e.pageY; + hoveract.data = e.data; + $document.on('mousemove.contextMenuShow', handle.mousemove); + hoveract.timer = setTimeout(function () { + hoveract.timer = null; + $document.off('mousemove.contextMenuShow'); + $currentTrigger = $this; + $this.trigger($.Event('contextmenu', { + data: hoveract.data, + pageX: hoveract.pageX, + pageY: hoveract.pageY + })); + }, e.data.delay); + }, + // contextMenu hover trigger + mousemove: function (e) { + hoveract.pageX = e.pageX; + hoveract.pageY = e.pageY; + }, + // contextMenu hover trigger + mouseleave: function (e) { + // abort if we're leaving for a menu + var $related = $(e.relatedTarget); + if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { + return; + } + + try { + clearTimeout(hoveract.timer); + } catch (e) { + } + + hoveract.timer = null; + }, + // click on layer to hide contextMenu + layerClick: function (e) { + var $this = $(this), + root = $this.data('contextMenuRoot'), + button = e.button, + x = e.pageX, + y = e.pageY, + fakeClick = x === undefined, + target, + offset; + + e.preventDefault(); + + setTimeout(function () { + // If the click is not real, things break: https://github.com/swisnl/jQuery-contextMenu/issues/132 + if(fakeClick){ + if (root !== null && typeof root !== 'undefined' && root.$menu !== null && typeof root.$menu !== 'undefined') { + root.$menu.trigger('contextmenu:hide'); + } + return; + } + + var $window; + var triggerAction = ((root.trigger === 'left' && button === 0) || (root.trigger === 'right' && button === 2)); + + // find the element that would've been clicked, wasn't the layer in the way + if (document.elementFromPoint && root.$layer) { + root.$layer.hide(); + target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop()); + + // also need to try and focus this element if we're in a contenteditable area, + // as the layer will prevent the browser mouse action we want + if (target.isContentEditable) { + var range = document.createRange(), + sel = window.getSelection(); + range.selectNode(target); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + } + $(target).trigger(e); + root.$layer.show(); + } + + if (root.hideOnSecondTrigger && triggerAction && root.$menu !== null && typeof root.$menu !== 'undefined') { + root.$menu.trigger('contextmenu:hide'); + return; + } + + if (root.reposition && triggerAction) { + if (document.elementFromPoint) { + if (root.$trigger.is(target)) { + root.position.call(root.$trigger, root, x, y); + return; + } + } else { + offset = root.$trigger.offset(); + $window = $(window); + // while this looks kinda awful, it's the best way to avoid + // unnecessarily calculating any positions + offset.top += $window.scrollTop(); + if (offset.top <= e.pageY) { + offset.left += $window.scrollLeft(); + if (offset.left <= e.pageX) { + offset.bottom = offset.top + root.$trigger.outerHeight(); + if (offset.bottom >= e.pageY) { + offset.right = offset.left + root.$trigger.outerWidth(); + if (offset.right >= e.pageX) { + // reposition + root.position.call(root.$trigger, root, x, y); + return; + } + } + } + } + } + } + + if (target && triggerAction) { + root.$trigger.one('contextmenu:hidden', function () { + $(target).contextMenu({x: x, y: y, button: button}); + }); + } + + if (root !== null && typeof root !== 'undefined' && root.$menu !== null && typeof root.$menu !== 'undefined') { + root.$menu.trigger('contextmenu:hide'); + } + }, 50); + }, + // key handled :hover + keyStop: function (e, opt) { + if (!opt.isInput) { + e.preventDefault(); + } + + e.stopPropagation(); + }, + key: function (e) { + + var opt = {}; + + // Only get the data from $currentTrigger if it exists + if ($currentTrigger) { + opt = $currentTrigger.data('contextMenu') || {}; + } + // If the trigger happen on a element that are above the contextmenu do this + if (typeof opt.zIndex === 'undefined') { + opt.zIndex = 0; + } + var targetZIndex = 0; + var getZIndexOfTriggerTarget = function (target) { + if (target.style.zIndex !== '') { + targetZIndex = target.style.zIndex; + } else { + if (target.offsetParent !== null && typeof target.offsetParent !== 'undefined') { + getZIndexOfTriggerTarget(target.offsetParent); + } + else if (target.parentElement !== null && typeof target.parentElement !== 'undefined') { + getZIndexOfTriggerTarget(target.parentElement); + } + } + }; + getZIndexOfTriggerTarget(e.target); + // If targetZIndex is heigher then opt.zIndex dont progress any futher. + // This is used to make sure that if you are using a dialog with a input / textarea / contenteditable div + // and its above the contextmenu it wont steal keys events + if (opt.$menu && parseInt(targetZIndex,10) > parseInt(opt.$menu.css("zIndex"),10)) { + return; + } + switch (e.keyCode) { + case 9: + case 38: // up + handle.keyStop(e, opt); + // if keyCode is [38 (up)] or [9 (tab) with shift] + if (opt.isInput) { + if (e.keyCode === 9 && e.shiftKey) { + e.preventDefault(); + if (opt.$selected) { + opt.$selected.find('input, textarea, select').blur(); + } + if (opt.$menu !== null && typeof opt.$menu !== 'undefined') { + opt.$menu.trigger('prevcommand'); + } + return; + } else if (e.keyCode === 38 && opt.$selected.find('input, textarea, select').prop('type') === 'checkbox') { + // checkboxes don't capture this key + e.preventDefault(); + return; + } + } else if (e.keyCode !== 9 || e.shiftKey) { + if (opt.$menu !== null && typeof opt.$menu !== 'undefined') { + opt.$menu.trigger('prevcommand'); + } + return; + } + break; + // omitting break; + // case 9: // tab - reached through omitted break; + case 40: // down + handle.keyStop(e, opt); + if (opt.isInput) { + if (e.keyCode === 9) { + e.preventDefault(); + if (opt.$selected) { + opt.$selected.find('input, textarea, select').blur(); + } + if (opt.$menu !== null && typeof opt.$menu !== 'undefined') { + opt.$menu.trigger('nextcommand'); + } + return; + } else if (e.keyCode === 40 && opt.$selected.find('input, textarea, select').prop('type') === 'checkbox') { + // checkboxes don't capture this key + e.preventDefault(); + return; + } + } else { + if (opt.$menu !== null && typeof opt.$menu !== 'undefined') { + opt.$menu.trigger('nextcommand'); + } + return; + } + break; + + case 37: // left + handle.keyStop(e, opt); + if (opt.isInput || !opt.$selected || !opt.$selected.length) { + break; + } + + if (!opt.$selected.parent().hasClass('context-menu-root')) { + var $parent = opt.$selected.parent().parent(); + opt.$selected.trigger('contextmenu:blur'); + opt.$selected = $parent; + return; + } + break; + + case 39: // right + handle.keyStop(e, opt); + if (opt.isInput || !opt.$selected || !opt.$selected.length) { + break; + } + + var itemdata = opt.$selected.data('contextMenu') || {}; + if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) { + opt.$selected = null; + itemdata.$selected = null; + itemdata.$menu.trigger('nextcommand'); + return; + } + break; + + case 35: // end + case 36: // home + if (opt.$selected && opt.$selected.find('input, textarea, select').length) { + return; + } else { + (opt.$selected && opt.$selected.parent() || opt.$menu) + .children(':not(.' + opt.classNames.disabled + ', .' + opt.classNames.notSelectable + ')')[e.keyCode === 36 ? 'first' : 'last']() + .trigger('contextmenu:focus'); + e.preventDefault(); + return; + } + break; + + case 13: // enter + handle.keyStop(e, opt); + if (opt.isInput) { + if (opt.$selected && !opt.$selected.is('textarea, select')) { + e.preventDefault(); + return; + } + break; + } + if (typeof opt.$selected !== 'undefined' && opt.$selected !== null) { + opt.$selected.trigger('mouseup'); + } + return; + + case 32: // space + case 33: // page up + case 34: // page down + // prevent browser from scrolling down while menu is visible + handle.keyStop(e, opt); + return; + + case 27: // esc + handle.keyStop(e, opt); + if (opt.$menu !== null && typeof opt.$menu !== 'undefined') { + opt.$menu.trigger('contextmenu:hide'); + } + return; + + default: // 0-9, a-z + var k = (String.fromCharCode(e.keyCode)).toUpperCase(); + if (opt.accesskeys && opt.accesskeys[k]) { + // according to the specs accesskeys must be invoked immediately + opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu ? 'contextmenu:focus' : 'mouseup'); + return; + } + break; + } + // pass event to selected item, + // stop propagation to avoid endless recursion + e.stopPropagation(); + if (typeof opt.$selected !== 'undefined' && opt.$selected !== null) { + opt.$selected.trigger(e); + } + }, + // select previous possible command in menu + prevItem: function (e) { + e.stopPropagation(); + var opt = $(this).data('contextMenu') || {}; + var root = $(this).data('contextMenuRoot') || {}; + + // obtain currently selected menu + if (opt.$selected) { + var $s = opt.$selected; + opt = opt.$selected.parent().data('contextMenu') || {}; + opt.$selected = $s; + } + + var $children = opt.$menu.children(), + $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(), + $round = $prev; + + // skip disabled or hidden elements + while ($prev.hasClass(root.classNames.disabled) || $prev.hasClass(root.classNames.notSelectable) || $prev.is(':hidden')) { + if ($prev.prev().length) { + $prev = $prev.prev(); + } else { + $prev = $children.last(); + } + if ($prev.is($round)) { + // break endless loop + return; + } + } + + // leave current + if (opt.$selected) { + handle.itemMouseleave.call(opt.$selected.get(0), e); + } + + // activate next + handle.itemMouseenter.call($prev.get(0), e); + + // focus input + var $input = $prev.find('input, textarea, select'); + if ($input.length) { + $input.focus(); + } + }, + // select next possible command in menu + nextItem: function (e) { + e.stopPropagation(); + var opt = $(this).data('contextMenu') || {}; + var root = $(this).data('contextMenuRoot') || {}; + + // obtain currently selected menu + if (opt.$selected) { + var $s = opt.$selected; + opt = opt.$selected.parent().data('contextMenu') || {}; + opt.$selected = $s; + } + + var $children = opt.$menu.children(), + $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(), + $round = $next; + + // skip disabled + while ($next.hasClass(root.classNames.disabled) || $next.hasClass(root.classNames.notSelectable) || $next.is(':hidden')) { + if ($next.next().length) { + $next = $next.next(); + } else { + $next = $children.first(); + } + if ($next.is($round)) { + // break endless loop + return; + } + } + + // leave current + if (opt.$selected) { + handle.itemMouseleave.call(opt.$selected.get(0), e); + } + + // activate next + handle.itemMouseenter.call($next.get(0), e); + + // focus input + var $input = $next.find('input, textarea, select'); + if ($input.length) { + $input.focus(); + } + }, + // flag that we're inside an input so the key handler can act accordingly + focusInput: function () { + var $this = $(this).closest('.context-menu-item'), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + root.$selected = opt.$selected = $this; + root.isInput = opt.isInput = true; + }, + // flag that we're inside an input so the key handler can act accordingly + blurInput: function () { + var $this = $(this).closest('.context-menu-item'), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + root.isInput = opt.isInput = false; + }, + // :hover on menu + menuMouseenter: function () { + var root = $(this).data().contextMenuRoot; + root.hovering = true; + }, + // :hover on menu + menuMouseleave: function (e) { + var root = $(this).data().contextMenuRoot; + if (root.$layer && root.$layer.is(e.relatedTarget)) { + root.hovering = false; + } + }, + // :hover done manually so key handling is possible + itemMouseenter: function (e) { + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + root.hovering = true; + + // abort if we're re-entering + if (e && root.$layer && root.$layer.is(e.relatedTarget)) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + + // make sure only one item is selected + (opt.$menu ? opt : root).$menu + .children('.' + root.classNames.hover).trigger('contextmenu:blur') + .children('.hover').trigger('contextmenu:blur'); + + if ($this.hasClass(root.classNames.disabled) || $this.hasClass(root.classNames.notSelectable)) { + opt.$selected = null; + return; + } + + + $this.trigger('contextmenu:focus'); + }, + // :hover done manually so key handling is possible + itemMouseleave: function (e) { + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) { + if (typeof root.$selected !== 'undefined' && root.$selected !== null) { + root.$selected.trigger('contextmenu:blur'); + } + e.preventDefault(); + e.stopImmediatePropagation(); + root.$selected = opt.$selected = opt.$node; + return; + } + + if(opt && opt.$menu && opt.$menu.hasClass('context-menu-visible')){ + return; + } + + $this.trigger('contextmenu:blur'); + }, + // contextMenu item click + itemClick: function (e) { + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot, + key = data.contextMenuKey, + callback; + + // abort if the key is unknown or disabled or is a menu + if (!opt.items[key] || $this.is('.' + root.classNames.disabled + ', .context-menu-separator, .' + root.classNames.notSelectable) || ($this.is('.context-menu-submenu') && root.selectableSubMenu === false )) { + return; + } + + e.preventDefault(); + e.stopImmediatePropagation(); + + if ($.isFunction(opt.callbacks[key]) && Object.prototype.hasOwnProperty.call(opt.callbacks, key)) { + // item-specific callback + callback = opt.callbacks[key]; + } else if ($.isFunction(root.callback)) { + // default callback + callback = root.callback; + } else { + // no callback, no action + return; + } + + // hide menu if callback doesn't stop that + if (callback.call(root.$trigger, key, root, e) !== false) { + root.$menu.trigger('contextmenu:hide'); + } else if (root.$menu.parent().length) { + op.update.call(root.$trigger, root); + } + }, + // ignore click events on input elements + inputClick: function (e) { + e.stopImmediatePropagation(); + }, + // hide <menu> + hideMenu: function (e, data) { + var root = $(this).data('contextMenuRoot'); + op.hide.call(root.$trigger, root, data && data.force); + }, + // focus <command> + focusItem: function (e) { + e.stopPropagation(); + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + if ($this.hasClass(root.classNames.disabled) || $this.hasClass(root.classNames.notSelectable)) { + return; + } + + $this + .addClass([root.classNames.hover, root.classNames.visible].join(' ')) + // select other items and included items + .parent().find('.context-menu-item').not($this) + .removeClass(root.classNames.visible) + .filter('.' + root.classNames.hover) + .trigger('contextmenu:blur'); + + // remember selected + opt.$selected = root.$selected = $this; + + + if(opt && opt.$node && opt.$node.hasClass('context-menu-submenu')){ + opt.$node.addClass(root.classNames.hover); + } + + // position sub-menu - do after show so dumb $.ui.position can keep up + if (opt.$node) { + root.positionSubmenu.call(opt.$node, opt.$menu); + } + }, + // blur <command> + blurItem: function (e) { + e.stopPropagation(); + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + if (opt.autoHide) { // for tablets and touch screens this needs to remain + $this.removeClass(root.classNames.visible); + } + $this.removeClass(root.classNames.hover); + opt.$selected = null; + } + }, + // operations + op = { + show: function (opt, x, y) { + var $trigger = $(this), + css = {}; + + // hide any open menus + $('#context-menu-layer').trigger('mousedown'); + + // backreference for callbacks + opt.$trigger = $trigger; + + // show event + if (opt.events.show.call($trigger, opt) === false) { + $currentTrigger = null; + return; + } + + // create or update context menu + var hasVisibleItems = op.update.call($trigger, opt); + if (hasVisibleItems === false) { + $currentTrigger = null; + return; + } + + // position menu + opt.position.call($trigger, opt, x, y); + + // make sure we're in front + if (opt.zIndex) { + var additionalZValue = opt.zIndex; + // If opt.zIndex is a function, call the function to get the right zIndex. + if (typeof opt.zIndex === 'function') { + additionalZValue = opt.zIndex.call($trigger, opt); + } + css.zIndex = zindex($trigger) + additionalZValue; + } + + // add layer + op.layer.call(opt.$menu, opt, css.zIndex); + + // adjust sub-menu zIndexes + opt.$menu.find('ul').css('zIndex', css.zIndex + 1); + + // position and show context menu + opt.$menu.css(css)[opt.animation.show](opt.animation.duration, function () { + $trigger.trigger('contextmenu:visible'); + + op.activated(opt); + opt.events.activated(opt); + }); + // make options available and set state + $trigger + .data('contextMenu', opt) + .addClass('context-menu-active'); + + // register key handler + $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key); + // register autoHide handler + if (opt.autoHide) { + // mouse position handler + $(document).on('mousemove.contextMenuAutoHide', function (e) { + // need to capture the offset on mousemove, + // since the page might've been scrolled since activation + var pos = $trigger.offset(); + pos.right = pos.left + $trigger.outerWidth(); + pos.bottom = pos.top + $trigger.outerHeight(); + + if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) { + /* Additional hover check after short time, you might just miss the edge of the menu */ + setTimeout(function () { + if (!opt.hovering && opt.$menu !== null && typeof opt.$menu !== 'undefined') { + opt.$menu.trigger('contextmenu:hide'); + } + }, 50); + } + }); + } + }, + hide: function (opt, force) { + var $trigger = $(this); + if (!opt) { + opt = $trigger.data('contextMenu') || {}; + } + + // hide event + if (!force && opt.events && opt.events.hide.call($trigger, opt) === false) { + return; + } + + // remove options and revert state + $trigger + .removeData('contextMenu') + .removeClass('context-menu-active'); + + if (opt.$layer) { + // keep layer for a bit so the contextmenu event can be aborted properly by opera + setTimeout((function ($layer) { + return function () { + $layer.remove(); + }; + })(opt.$layer), 10); + + try { + delete opt.$layer; + } catch (e) { + opt.$layer = null; + } + } + + // remove handle + $currentTrigger = null; + // remove selected + opt.$menu.find('.' + opt.classNames.hover).trigger('contextmenu:blur'); + opt.$selected = null; + // collapse all submenus + opt.$menu.find('.' + opt.classNames.visible).removeClass(opt.classNames.visible); + // unregister key and mouse handlers + // $(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705 + $(document).off('.contextMenuAutoHide').off('keydown.contextMenu'); + // hide menu + if (opt.$menu) { + opt.$menu[opt.animation.hide](opt.animation.duration, function () { + // tear down dynamically built menu after animation is completed. + if (opt.build) { + opt.$menu.remove(); + $.each(opt, function (key) { + switch (key) { + case 'ns': + case 'selector': + case 'build': + case 'trigger': + return true; + + default: + opt[key] = undefined; + try { + delete opt[key]; + } catch (e) { + } + return true; + } + }); + } + + setTimeout(function () { + $trigger.trigger('contextmenu:hidden'); + }, 10); + }); + } + }, + create: function (opt, root) { + if (typeof root === 'undefined') { + root = opt; + } + + // create contextMenu + opt.$menu = $('<ul class="context-menu-list"></ul>').addClass(opt.className || '').data({ + 'contextMenu': opt, + 'contextMenuRoot': root + }); + + $.each(['callbacks', 'commands', 'inputs'], function (i, k) { + opt[k] = {}; + if (!root[k]) { + root[k] = {}; + } + }); + + if (!root.accesskeys) { + root.accesskeys = {}; + } + + function createNameNode(item) { + var $name = $('<span></span>'); + if (item._accesskey) { + if (item._beforeAccesskey) { + $name.append(document.createTextNode(item._beforeAccesskey)); + } + $('<span></span>') + .addClass('context-menu-accesskey') + .text(item._accesskey) + .appendTo($name); + if (item._afterAccesskey) { + $name.append(document.createTextNode(item._afterAccesskey)); + } + } else { + if (item.isHtmlName) { + // restrict use with access keys + if (typeof item.accesskey !== 'undefined') { + throw new Error('accesskeys are not compatible with HTML names and cannot be used together in the same item'); + } + $name.html(item.name); + } else { + $name.text(item.name); + } + } + return $name; + } + + // create contextMenu items + $.each(opt.items, function (key, item) { + var $t = $('<li class="context-menu-item"></li>').addClass(item.className || ''), + $label = null, + $input = null; + + // iOS needs to see a click-event bound to an element to actually + // have the TouchEvents infrastructure trigger the click event + $t.on('click', $.noop); + + // Make old school string seperator a real item so checks wont be + // akward later. + // And normalize 'cm_separator' into 'cm_seperator'. + if (typeof item === 'string' || item.type === 'cm_separator') { + item = {type: 'cm_seperator'}; + } + + item.$node = $t.data({ + 'contextMenu': opt, + 'contextMenuRoot': root, + 'contextMenuKey': key + }); + + // register accesskey + // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that + if (typeof item.accesskey !== 'undefined') { + var aks = splitAccesskey(item.accesskey); + for (var i = 0, ak; ak = aks[i]; i++) { + if (!root.accesskeys[ak]) { + root.accesskeys[ak] = item; + var matched = item.name.match(new RegExp('^(.*?)(' + ak + ')(.*)$', 'i')); + if (matched) { + item._beforeAccesskey = matched[1]; + item._accesskey = matched[2]; + item._afterAccesskey = matched[3]; + } + break; + } + } + } + + if (item.type && types[item.type]) { + // run custom type handler + types[item.type].call($t, item, opt, root); + // register commands + $.each([opt, root], function (i, k) { + k.commands[key] = item; + // Overwrite only if undefined or the item is appended to the root. This so it + // doesn't overwrite callbacks of root elements if the name is the same. + if ($.isFunction(item.callback) && (typeof k.callbacks[key] === 'undefined' || typeof opt.type === 'undefined')) { + k.callbacks[key] = item.callback; + } + }); + } else { + // add label for input + if (item.type === 'cm_seperator') { + $t.addClass('context-menu-separator ' + root.classNames.notSelectable); + } else if (item.type === 'html') { + $t.addClass('context-menu-html ' + root.classNames.notSelectable); + } else if (item.type !== 'sub' && item.type) { + $label = $('<label></label>').appendTo($t); + createNameNode(item).appendTo($label); + + $t.addClass('context-menu-input'); + opt.hasTypes = true; + $.each([opt, root], function (i, k) { + k.commands[key] = item; + k.inputs[key] = item; + }); + } else if (item.items) { + item.type = 'sub'; + } + + switch (item.type) { + case 'cm_seperator': + break; + + case 'text': + $input = $('<input type="text" value="1" name="" />') + .attr('name', 'context-menu-input-' + key) + .val(item.value || '') + .appendTo($label); + break; + + case 'textarea': + $input = $('<textarea name=""></textarea>') + .attr('name', 'context-menu-input-' + key) + .val(item.value || '') + .appendTo($label); + + if (item.height) { + $input.height(item.height); + } + break; + + case 'checkbox': + $input = $('<input type="checkbox" value="1" name="" />') + .attr('name', 'context-menu-input-' + key) + .val(item.value || '') + .prop('checked', !!item.selected) + .prependTo($label); + break; + + case 'radio': + $input = $('<input type="radio" value="1" name="" />') + .attr('name', 'context-menu-input-' + item.radio) + .val(item.value || '') + .prop('checked', !!item.selected) + .prependTo($label); + break; + + case 'select': + $input = $('<select name=""></select>') + .attr('name', 'context-menu-input-' + key) + .appendTo($label); + if (item.options) { + $.each(item.options, function (value, text) { + $('<option></option>').val(value).text(text).appendTo($input); + }); + $input.val(item.selected); + } + break; + + case 'sub': + createNameNode(item).appendTo($t); + item.appendTo = item.$node; + $t.data('contextMenu', item).addClass('context-menu-submenu'); + item.callback = null; + + // If item contains items, and this is a promise, we should create it later + // check if subitems is of type promise. If it is a promise we need to create + // it later, after promise has been resolved. + if ('function' === typeof item.items.then) { + // probably a promise, process it, when completed it will create the sub menu's. + op.processPromises(item, root, item.items); + } else { + // normal submenu. + op.create(item, root); + } + break; + + case 'html': + $(item.html).appendTo($t); + break; + + default: + $.each([opt, root], function (i, k) { + k.commands[key] = item; + // Overwrite only if undefined or the item is appended to the root. This so it + // doesn't overwrite callbacks of root elements if the name is the same. + if ($.isFunction(item.callback) && (typeof k.callbacks[key] === 'undefined' || typeof opt.type === 'undefined')) { + k.callbacks[key] = item.callback; + } + }); + createNameNode(item).appendTo($t); + break; + } + + // disable key listener in <input> + if (item.type && item.type !== 'sub' && item.type !== 'html' && item.type !== 'cm_seperator') { + $input + .on('focus', handle.focusInput) + .on('blur', handle.blurInput); + + if (item.events) { + $input.on(item.events, opt); + } + } + + // add icons + if (item.icon) { + if ($.isFunction(item.icon)) { + item._icon = item.icon.call(this, this, $t, key, item); + } else { + if (typeof(item.icon) === 'string' && ( + item.icon.substring(0, 4) === 'fab ' + || item.icon.substring(0, 4) === 'fas ' + || item.icon.substring(0, 4) === 'far ' + || item.icon.substring(0, 4) === 'fal ') + ) { + // to enable font awesome + $t.addClass(root.classNames.icon + ' ' + root.classNames.icon + '--fa5'); + item._icon = $('<i class="' + item.icon + '"></i>'); + } else if (typeof(item.icon) === 'string' && item.icon.substring(0, 3) === 'fa-') { + item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon; + } else { + item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon; + } + } + + if(typeof(item._icon) === "string"){ + $t.addClass(item._icon); + } else { + $t.prepend(item._icon); + } + } + } + + // cache contained elements + item.$input = $input; + item.$label = $label; + + // attach item to menu + $t.appendTo(opt.$menu); + + // Disable text selection + if (!opt.hasTypes && $.support.eventSelectstart) { + // browsers support user-select: none, + // IE has a special event for text-selection + // browsers supporting neither will not be preventing text-selection + $t.on('selectstart.disableTextSelect', handle.abortevent); + } + }); + // attach contextMenu to <body> (to bypass any possible overflow:hidden issues on parents of the trigger element) + if (!opt.$node) { + opt.$menu.css('display', 'none').addClass('context-menu-root'); + } + opt.$menu.appendTo(opt.appendTo || document.body); + }, + resize: function ($menu, nested) { + var domMenu; + // determine widths of submenus, as CSS won't grow them automatically + // position:absolute within position:absolute; min-width:100; max-width:200; results in width: 100; + // kinda sucks hard... + + // determine width of absolutely positioned element + $menu.css({position: 'absolute', display: 'block'}); + // don't apply yet, because that would break nested elements' widths + $menu.data('width', + (domMenu = $menu.get(0)).getBoundingClientRect ? + Math.ceil(domMenu.getBoundingClientRect().width) : + $menu.outerWidth() + 1); // outerWidth() returns rounded pixels + // reset styles so they allow nested elements to grow/shrink naturally + $menu.css({ + position: 'static', + minWidth: '0px', + maxWidth: '100000px' + }); + // identify width of nested menus + $menu.find('> li > ul').each(function () { + op.resize($(this), true); + }); + // reset and apply changes in the end because nested + // elements' widths wouldn't be calculatable otherwise + if (!nested) { + $menu.find('ul').addBack().css({ + position: '', + display: '', + minWidth: '', + maxWidth: '' + }).outerWidth(function () { + return $(this).data('width'); + }); + } + }, + update: function (opt, root) { + var $trigger = this; + if (typeof root === 'undefined') { + root = opt; + op.resize(opt.$menu); + } + + var hasVisibleItems = false; + + // re-check disabled for each item + opt.$menu.children().each(function () { + var $item = $(this), + key = $item.data('contextMenuKey'), + item = opt.items[key], + disabled = ($.isFunction(item.disabled) && item.disabled.call($trigger, key, root)) || item.disabled === true, + visible; + if ($.isFunction(item.visible)) { + visible = item.visible.call($trigger, key, root); + } else if (typeof item.visible !== 'undefined') { + visible = item.visible === true; + } else { + visible = true; + } + + if (visible) { + hasVisibleItems = true; + } + + $item[visible ? 'show' : 'hide'](); + + // dis- / enable item + $item[disabled ? 'addClass' : 'removeClass'](root.classNames.disabled); + + if ($.isFunction(item.icon)) { + $item.removeClass(item._icon); + var iconResult = item.icon.call(this, $trigger, $item, key, item); + if(typeof(iconResult) === "string"){ + $item.addClass(iconResult); + } else { + $item.prepend(iconResult); + } + } + + if (item.type) { + // dis- / enable input elements + $item.find('input, select, textarea').prop('disabled', disabled); + + // update input states + switch (item.type) { + case 'text': + case 'textarea': + item.$input.val(item.value || ''); + break; + + case 'checkbox': + case 'radio': + item.$input.val(item.value || '').prop('checked', !!item.selected); + break; + + case 'select': + item.$input.val((item.selected === 0 ? "0" : item.selected) || ''); + break; + } + } + + if (item.$menu) { + // update sub-menu + var subMenuHasVisibleItems = op.update.call($trigger, item, root); + if (subMenuHasVisibleItems) { + hasVisibleItems = true; + } + } + }); + return hasVisibleItems; + }, + layer: function (opt, zIndex) { + // add transparent layer for click area + // filter and background for Internet Explorer, Issue #23 + var $layer = opt.$layer = $('<div id="context-menu-layer"></div>') + .css({ + height: $win.height(), + width: $win.width(), + display: 'block', + position: 'fixed', + 'z-index': zIndex, + top: 0, + left: 0, + opacity: 0, + filter: 'alpha(opacity=0)', + 'background-color': '#000' + }) + .data('contextMenuRoot', opt) + .insertBefore(this) + .on('contextmenu', handle.abortevent) + .on('mousedown', handle.layerClick); + + // IE6 doesn't know position:fixed; + if (typeof document.body.style.maxWidth === 'undefined') { // IE6 doesn't support maxWidth + $layer.css({ + 'position': 'absolute', + 'height': $(document).height() + }); + } + + return $layer; + }, + processPromises: function (opt, root, promise) { + // Start + opt.$node.addClass(root.classNames.iconLoadingClass); + + function completedPromise(opt, root, items) { + // Completed promise (dev called promise.resolve). We now have a list of items which can + // be used to create the rest of the context menu. + if (typeof items === 'undefined') { + // Null result, dev should have checked + errorPromise(undefined);//own error object + } + finishPromiseProcess(opt, root, items); + } + + function errorPromise(opt, root, errorItem) { + // User called promise.reject() with an error item, if not, provide own error item. + if (typeof errorItem === 'undefined') { + errorItem = { + "error": { + name: "No items and no error item", + icon: "context-menu-icon context-menu-icon-quit" + } + }; + if (window.console) { + (console.error || console.log).call(console, 'When you reject a promise, provide an "items" object, equal to normal sub-menu items'); + } + } else if (typeof errorItem === 'string') { + errorItem = {"error": {name: errorItem}}; + } + finishPromiseProcess(opt, root, errorItem); + } + + function finishPromiseProcess(opt, root, items) { + if (typeof root.$menu === 'undefined' || !root.$menu.is(':visible')) { + return; + } + opt.$node.removeClass(root.classNames.iconLoadingClass); + opt.items = items; + op.create(opt, root, true); // Create submenu + op.update(opt, root); // Correctly update position if user is already hovered over menu item + root.positionSubmenu.call(opt.$node, opt.$menu); // positionSubmenu, will only do anything if user already hovered over menu item that just got new subitems. + } + + // Wait for promise completion. .then(success, error, notify) (we don't track notify). Bind the opt + // and root to avoid scope problems + promise.then(completedPromise.bind(this, opt, root), errorPromise.bind(this, opt, root)); + }, + // operation that will run after contextMenu showed on screen + activated: function(opt){ + var $menu = opt.$menu; + var $menuOffset = $menu.offset(); + var winHeight = $(window).height(); + var winScrollTop = $(window).scrollTop(); + var menuHeight = $menu.height(); + if(menuHeight > winHeight){ + $menu.css({ + 'height' : winHeight + 'px', + 'overflow-x': 'hidden', + 'overflow-y': 'auto', + 'top': winScrollTop + 'px' + }); + } else if(($menuOffset.top < winScrollTop) || ($menuOffset.top + menuHeight > winScrollTop + winHeight)){ + $menu.css({ + 'top': winScrollTop + 'px' + }); + } + } + }; + + // split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key + function splitAccesskey(val) { + var t = val.split(/\s+/); + var keys = []; + + for (var i = 0, k; k = t[i]; i++) { + k = k.charAt(0).toUpperCase(); // first character only + // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it. + // a map to look up already used access keys would be nice + keys.push(k); + } + + return keys; + } + +// handle contextMenu triggers + $.fn.contextMenu = function (operation) { + var $t = this, $o = operation; + if (this.length > 0) { // this is not a build on demand menu + if (typeof operation === 'undefined') { + this.first().trigger('contextmenu'); + } else if (typeof operation.x !== 'undefined' && typeof operation.y !== 'undefined') { + this.first().trigger($.Event('contextmenu', { + pageX: operation.x, + pageY: operation.y, + mouseButton: operation.button + })); + } else if (operation === 'hide') { + var $menu = this.first().data('contextMenu') ? this.first().data('contextMenu').$menu : null; + if ($menu) { + $menu.trigger('contextmenu:hide'); + } + } else if (operation === 'destroy') { + $.contextMenu('destroy', {context: this}); + } else if ($.isPlainObject(operation)) { + operation.context = this; + $.contextMenu('create', operation); + } else if (operation) { + this.removeClass('context-menu-disabled'); + } else if (!operation) { + this.addClass('context-menu-disabled'); + } + } else { + $.each(menus, function () { + if (this.selector === $t.selector) { + $o.data = this; + + $.extend($o.data, {trigger: 'demand'}); + } + }); + + handle.contextmenu.call($o.target, $o); + } + + return this; + }; + + // manage contextMenu instances + $.contextMenu = function (operation, options) { + if (typeof operation !== 'string') { + options = operation; + operation = 'create'; + } + + if (typeof options === 'string') { + options = {selector: options}; + } else if (typeof options === 'undefined') { + options = {}; + } + + // merge with default options + var o = $.extend(true, {}, defaults, options || {}); + var $document = $(document); + var $context = $document; + var _hasContext = false; + + if (!o.context || !o.context.length) { + o.context = document; + } else { + // you never know what they throw at you... + $context = $(o.context).first(); + o.context = $context.get(0); + _hasContext = !$(o.context).is(document); + } + + switch (operation) { + + case 'update': + // Updates visibility and such + if(_hasContext){ + op.update($context); + } else { + for(var menu in menus){ + if(menus.hasOwnProperty(menu)){ + op.update(menus[menu]); + } + } + } + break; + + case 'create': + // no selector no joy + if (!o.selector) { + throw new Error('No selector specified'); + } + // make sure internal classes are not bound to + if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) { + throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className'); + } + if (!o.build && (!o.items || $.isEmptyObject(o.items))) { + throw new Error('No Items specified'); + } + counter++; + o.ns = '.contextMenu' + counter; + if (!_hasContext) { + namespaces[o.selector] = o.ns; + } + menus[o.ns] = o; + + // default to right click + if (!o.trigger) { + o.trigger = 'right'; + } + + if (!initialized) { + var itemClick = o.itemClickEvent === 'click' ? 'click.contextMenu' : 'mouseup.contextMenu'; + var contextMenuItemObj = { + // 'mouseup.contextMenu': handle.itemClick, + // 'click.contextMenu': handle.itemClick, + 'contextmenu:focus.contextMenu': handle.focusItem, + 'contextmenu:blur.contextMenu': handle.blurItem, + 'contextmenu.contextMenu': handle.abortevent, + 'mouseenter.contextMenu': handle.itemMouseenter, + 'mouseleave.contextMenu': handle.itemMouseleave + }; + contextMenuItemObj[itemClick] = handle.itemClick; + // make sure item click is registered first + $document + .on({ + 'contextmenu:hide.contextMenu': handle.hideMenu, + 'prevcommand.contextMenu': handle.prevItem, + 'nextcommand.contextMenu': handle.nextItem, + 'contextmenu.contextMenu': handle.abortevent, + 'mouseenter.contextMenu': handle.menuMouseenter, + 'mouseleave.contextMenu': handle.menuMouseleave + }, '.context-menu-list') + .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick) + .on(contextMenuItemObj, '.context-menu-item'); + + initialized = true; + } + + // engage native contextmenu event + $context + .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu); + + if (_hasContext) { + // add remove hook, just in case + $context.on('remove' + o.ns, function () { + $(this).contextMenu('destroy'); + }); + } + + switch (o.trigger) { + case 'hover': + $context + .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter) + .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave); + break; + + case 'left': + $context.on('click' + o.ns, o.selector, o, handle.click); + break; + case 'touchstart': + $context.on('touchstart' + o.ns, o.selector, o, handle.click); + break; + /* + default: + // http://www.quirksmode.org/dom/events/contextmenu.html + $document + .on('mousedown' + o.ns, o.selector, o, handle.mousedown) + .on('mouseup' + o.ns, o.selector, o, handle.mouseup); + break; + */ + } + + // create menu + if (!o.build) { + op.create(o); + } + break; + + case 'destroy': + var $visibleMenu; + if (_hasContext) { + // get proper options + var context = o.context; + $.each(menus, function (ns, o) { + + if (!o) { + return true; + } + + // Is this menu equest to the context called from + if (!$(context).is(o.selector)) { + return true; + } + + $visibleMenu = $('.context-menu-list').filter(':visible'); + if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is($(o.context).find(o.selector))) { + $visibleMenu.trigger('contextmenu:hide', {force: true}); + } + + try { + if (menus[o.ns].$menu) { + menus[o.ns].$menu.remove(); + } + + delete menus[o.ns]; + } catch (e) { + menus[o.ns] = null; + } + + $(o.context).off(o.ns); + + return true; + }); + } else if (!o.selector) { + $document.off('.contextMenu .contextMenuAutoHide'); + $.each(menus, function (ns, o) { + $(o.context).off(o.ns); + }); + + namespaces = {}; + menus = {}; + counter = 0; + initialized = false; + + $('#context-menu-layer, .context-menu-list').remove(); + } else if (namespaces[o.selector]) { + $visibleMenu = $('.context-menu-list').filter(':visible'); + if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) { + $visibleMenu.trigger('contextmenu:hide', {force: true}); + } + + try { + if (menus[namespaces[o.selector]].$menu) { + menus[namespaces[o.selector]].$menu.remove(); + } + + delete menus[namespaces[o.selector]]; + } catch (e) { + menus[namespaces[o.selector]] = null; + } + + $document.off(namespaces[o.selector]); + } + break; + + case 'html5': + // if <command> and <menuitem> are not handled by the browser, + // or options was a bool true, + // initialize $.contextMenu for them + if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options === 'boolean' && options)) { + $('menu[type="context"]').each(function () { + if (this.id) { + $.contextMenu({ + selector: '[contextmenu=' + this.id + ']', + items: $.contextMenu.fromMenu(this) + }); + } + }).css('display', 'none'); + } + break; + + default: + throw new Error('Unknown operation "' + operation + '"'); + } + + return this; + }; + +// import values into <input> commands + $.contextMenu.setInputValues = function (opt, data) { + if (typeof data === 'undefined') { + data = {}; + } + + $.each(opt.inputs, function (key, item) { + switch (item.type) { + case 'text': + case 'textarea': + item.value = data[key] || ''; + break; + + case 'checkbox': + item.selected = data[key] ? true : false; + break; + + case 'radio': + item.selected = (data[item.radio] || '') === item.value; + break; + + case 'select': + item.selected = data[key] || ''; + break; + } + }); + }; + +// export values from <input> commands + $.contextMenu.getInputValues = function (opt, data) { + if (typeof data === 'undefined') { + data = {}; + } + + $.each(opt.inputs, function (key, item) { + switch (item.type) { + case 'text': + case 'textarea': + case 'select': + data[key] = item.$input.val(); + break; + + case 'checkbox': + data[key] = item.$input.prop('checked'); + break; + + case 'radio': + if (item.$input.prop('checked')) { + data[item.radio] = item.value; + } + break; + } + }); + + return data; + }; + +// find <label for="xyz"> + function inputLabel(node) { + return (node.id && $('label[for="' + node.id + '"]').val()) || node.name; + } + +// convert <menu> to items object + function menuChildren(items, $children, counter) { + if (!counter) { + counter = 0; + } + + $children.each(function () { + var $node = $(this), + node = this, + nodeName = this.nodeName.toLowerCase(), + label, + item; + + // extract <label><input> + if (nodeName === 'label' && $node.find('input, textarea, select').length) { + label = $node.text(); + $node = $node.children().first(); + node = $node.get(0); + nodeName = node.nodeName.toLowerCase(); + } + + /* + * <menu> accepts flow-content as children. that means <embed>, <canvas> and such are valid menu items. + * Not being the sadistic kind, $.contextMenu only accepts: + * <command>, <menuitem>, <hr>, <span>, <p> <input [text, radio, checkbox]>, <textarea>, <select> and of course <menu>. + * Everything else will be imported as an html node, which is not interfaced with contextMenu. + */ + + // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#concept-command + switch (nodeName) { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element + case 'menu': + item = {name: $node.attr('label'), items: {}}; + counter = menuChildren(item.items, $node.children(), counter); + break; + + // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command + case 'a': + // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command + case 'button': + item = { + name: $node.text(), + disabled: !!$node.attr('disabled'), + callback: (function () { + return function () { + $node.get(0).click(); + }; + })() + }; + break; + + // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-command-element-to-define-a-command + case 'menuitem': + case 'command': + switch ($node.attr('type')) { + case undefined: + case 'command': + case 'menuitem': + item = { + name: $node.attr('label'), + disabled: !!$node.attr('disabled'), + icon: $node.attr('icon'), + callback: (function () { + return function () { + $node.get(0).click(); + }; + })() + }; + break; + + case 'checkbox': + item = { + type: 'checkbox', + disabled: !!$node.attr('disabled'), + name: $node.attr('label'), + selected: !!$node.attr('checked') + }; + break; + case 'radio': + item = { + type: 'radio', + disabled: !!$node.attr('disabled'), + name: $node.attr('label'), + radio: $node.attr('radiogroup'), + value: $node.attr('id'), + selected: !!$node.attr('checked') + }; + break; + + default: + item = undefined; + } + break; + + case 'hr': + item = '-------'; + break; + + case 'input': + switch ($node.attr('type')) { + case 'text': + item = { + type: 'text', + name: label || inputLabel(node), + disabled: !!$node.attr('disabled'), + value: $node.val() + }; + break; + + case 'checkbox': + item = { + type: 'checkbox', + name: label || inputLabel(node), + disabled: !!$node.attr('disabled'), + selected: !!$node.attr('checked') + }; + break; + + case 'radio': + item = { + type: 'radio', + name: label || inputLabel(node), + disabled: !!$node.attr('disabled'), + radio: !!$node.attr('name'), + value: $node.val(), + selected: !!$node.attr('checked') + }; + break; + + default: + item = undefined; + break; + } + break; + + case 'select': + item = { + type: 'select', + name: label || inputLabel(node), + disabled: !!$node.attr('disabled'), + selected: $node.val(), + options: {} + }; + $node.children().each(function () { + item.options[this.value] = $(this).text(); + }); + break; + + case 'textarea': + item = { + type: 'textarea', + name: label || inputLabel(node), + disabled: !!$node.attr('disabled'), + value: $node.val() + }; + break; + + case 'label': + break; + + default: + item = {type: 'html', html: $node.clone(true)}; + break; + } + + if (item) { + counter++; + items['key' + counter] = item; + } + }); + + return counter; + } + +// convert html5 menu + $.contextMenu.fromMenu = function (element) { + var $this = $(element), + items = {}; + + menuChildren(items, $this.children()); + + return items; + }; + +// make defaults accessible + $.contextMenu.defaults = defaults; + $.contextMenu.types = types; +// export internal functions - undocumented, for hacking only! + $.contextMenu.handle = handle; + $.contextMenu.op = op; + $.contextMenu.menus = menus; +}); diff --git a/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryRangy.js b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryRangy.js new file mode 100644 index 0000000000000000000000000000000000000000..6ccf6c602cdfff95acf7e339ae9e7906892a8aaf --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryRangy.js @@ -0,0 +1,3845 @@ +/** + * Rangy, a cross-browser JavaScript range and selection library + * https://github.com/timdown/rangy + * + * Copyright 2015, Tim Down + * Licensed under the MIT license. + * Version: 1.3.0 + * Build date: 10 May 2015 + */ + +(function(factory, root) { + if (typeof define == "function" && define.amd) { + // AMD. Register as an anonymous module. + define(factory); + } else if (typeof module != "undefined" && typeof exports == "object") { + // Node/CommonJS style + module.exports = factory(); + } else { + // No AMD or CommonJS support so we place Rangy in (probably) the global variable + root.rangy = factory(); + } +})(function() { + + var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; + + // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START + // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113. + var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", + "commonAncestorContainer"]; + + // Minimal set of methods required for DOM Level 2 Range compliance + var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", + "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", + "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; + + var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; + + // Subset of TextRange's full set of methods that we're interested in + var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select", + "setEndPoint", "getBoundingClientRect"]; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Trio of functions taken from Peter Michaux's article: + // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting + function isHostMethod(o, p) { + var t = typeof o[p]; + return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; + } + + function isHostObject(o, p) { + return !!(typeof o[p] == OBJECT && o[p]); + } + + function isHostProperty(o, p) { + return typeof o[p] != UNDEFINED; + } + + // Creates a convenience function to save verbose repeated calls to tests functions + function createMultiplePropertyTest(testFunc) { + return function(o, props) { + var i = props.length; + while (i--) { + if (!testFunc(o, props[i])) { + return false; + } + } + return true; + }; + } + + // Next trio of functions are a convenience to save verbose repeated calls to previous two functions + var areHostMethods = createMultiplePropertyTest(isHostMethod); + var areHostObjects = createMultiplePropertyTest(isHostObject); + var areHostProperties = createMultiplePropertyTest(isHostProperty); + + function isTextRange(range) { + return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); + } + + function getBody(doc) { + return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; + } + + var forEach = [].forEach ? + function(arr, func) { + arr.forEach(func); + } : + function(arr, func) { + for (var i = 0, len = arr.length; i < len; ++i) { + func(arr[i], i); + } + }; + + var modules = {}; + + var isBrowser = (typeof window != UNDEFINED && typeof document != UNDEFINED); + + var util = { + isHostMethod: isHostMethod, + isHostObject: isHostObject, + isHostProperty: isHostProperty, + areHostMethods: areHostMethods, + areHostObjects: areHostObjects, + areHostProperties: areHostProperties, + isTextRange: isTextRange, + getBody: getBody, + forEach: forEach + }; + + var api = { + version: "1.3.0", + initialized: false, + isBrowser: isBrowser, + supported: true, + util: util, + features: {}, + modules: modules, + config: { + alertOnFail: false, + alertOnWarn: false, + preferTextRange: false, + autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize + } + }; + + function consoleLog(msg) { + if (typeof console != UNDEFINED && isHostMethod(console, "log")) { + console.log(msg); + } + } + + function alertOrLog(msg, shouldAlert) { + if (isBrowser && shouldAlert) { + alert(msg); + } else { + consoleLog(msg); + } + } + + function fail(reason) { + api.initialized = true; + api.supported = false; + alertOrLog("Rangy is not supported in this environment. Reason: " + reason, api.config.alertOnFail); + } + + api.fail = fail; + + function warn(msg) { + alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn); + } + + api.warn = warn; + + // Add utility extend() method + var extend; + if ({}.hasOwnProperty) { + util.extend = extend = function(obj, props, deep) { + var o, p; + for (var i in props) { + if (props.hasOwnProperty(i)) { + o = obj[i]; + p = props[i]; + if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") { + extend(o, p, true); + } + obj[i] = p; + } + } + // Special case for toString, which does not show up in for...in loops in IE <= 8 + if (props.hasOwnProperty("toString")) { + obj.toString = props.toString; + } + return obj; + }; + + util.createOptions = function(optionsParam, defaults) { + var options = {}; + extend(options, defaults); + if (optionsParam) { + extend(options, optionsParam); + } + return options; + }; + } else { + fail("hasOwnProperty not supported"); + } + + // Test whether we're in a browser and bail out if not + if (!isBrowser) { + fail("Rangy can only run in a browser"); + } + + // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not + (function() { + var toArray; + + if (isBrowser) { + var el = document.createElement("div"); + el.appendChild(document.createElement("span")); + var slice = [].slice; + try { + if (slice.call(el.childNodes, 0)[0].nodeType == 1) { + toArray = function(arrayLike) { + return slice.call(arrayLike, 0); + }; + } + } catch (e) {} + } + + if (!toArray) { + toArray = function(arrayLike) { + var arr = []; + for (var i = 0, len = arrayLike.length; i < len; ++i) { + arr[i] = arrayLike[i]; + } + return arr; + }; + } + + util.toArray = toArray; + })(); + + // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or + // normalization of event properties + var addListener; + if (isBrowser) { + if (isHostMethod(document, "addEventListener")) { + addListener = function(obj, eventType, listener) { + obj.addEventListener(eventType, listener, false); + }; + } else if (isHostMethod(document, "attachEvent")) { + addListener = function(obj, eventType, listener) { + obj.attachEvent("on" + eventType, listener); + }; + } else { + fail("Document does not have required addEventListener or attachEvent method"); + } + + util.addListener = addListener; + } + + var initListeners = []; + + function getErrorDesc(ex) { + return ex.message || ex.description || String(ex); + } + + // Initialization + function init() { + if (!isBrowser || api.initialized) { + return; + } + var testRange; + var implementsDomRange = false, implementsTextRange = false; + + // First, perform basic feature tests + + if (isHostMethod(document, "createRange")) { + testRange = document.createRange(); + if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { + implementsDomRange = true; + } + } + + var body = getBody(document); + if (!body || body.nodeName.toLowerCase() != "body") { + fail("No body element found"); + return; + } + + if (body && isHostMethod(body, "createTextRange")) { + testRange = body.createTextRange(); + if (isTextRange(testRange)) { + implementsTextRange = true; + } + } + + if (!implementsDomRange && !implementsTextRange) { + fail("Neither Range nor TextRange are available"); + return; + } + + api.initialized = true; + api.features = { + implementsDomRange: implementsDomRange, + implementsTextRange: implementsTextRange + }; + + // Initialize modules + var module, errorMessage; + for (var moduleName in modules) { + if ( (module = modules[moduleName]) instanceof Module ) { + module.init(module, api); + } + } + + // Call init listeners + for (var i = 0, len = initListeners.length; i < len; ++i) { + try { + initListeners[i](api); + } catch (ex) { + errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex); + consoleLog(errorMessage); + } + } + } + + function deprecationNotice(deprecated, replacement, module) { + if (module) { + deprecated += " in module " + module.name; + } + api.warn("DEPRECATED: " + deprecated + " is deprecated. Please use " + + replacement + " instead."); + } + + function createAliasForDeprecatedMethod(owner, deprecated, replacement, module) { + owner[deprecated] = function() { + deprecationNotice(deprecated, replacement, module); + return owner[replacement].apply(owner, util.toArray(arguments)); + }; + } + + util.deprecationNotice = deprecationNotice; + util.createAliasForDeprecatedMethod = createAliasForDeprecatedMethod; + + // Allow external scripts to initialize this library in case it's loaded after the document has loaded + api.init = init; + + // Execute listener immediately if already initialized + api.addInitListener = function(listener) { + if (api.initialized) { + listener(api); + } else { + initListeners.push(listener); + } + }; + + var shimListeners = []; + + api.addShimListener = function(listener) { + shimListeners.push(listener); + }; + + function shim(win) { + win = win || window; + init(); + + // Notify listeners + for (var i = 0, len = shimListeners.length; i < len; ++i) { + shimListeners[i](win); + } + } + + if (isBrowser) { + api.shim = api.createMissingNativeApi = shim; + createAliasForDeprecatedMethod(api, "createMissingNativeApi", "shim"); + } + + function Module(name, dependencies, initializer) { + this.name = name; + this.dependencies = dependencies; + this.initialized = false; + this.supported = false; + this.initializer = initializer; + } + + Module.prototype = { + init: function() { + var requiredModuleNames = this.dependencies || []; + for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) { + moduleName = requiredModuleNames[i]; + + requiredModule = modules[moduleName]; + if (!requiredModule || !(requiredModule instanceof Module)) { + throw new Error("required module '" + moduleName + "' not found"); + } + + requiredModule.init(); + + if (!requiredModule.supported) { + throw new Error("required module '" + moduleName + "' not supported"); + } + } + + // Now run initializer + this.initializer(this); + }, + + fail: function(reason) { + this.initialized = true; + this.supported = false; + throw new Error(reason); + }, + + warn: function(msg) { + api.warn("Module " + this.name + ": " + msg); + }, + + deprecationNotice: function(deprecated, replacement) { + api.warn("DEPRECATED: " + deprecated + " in module " + this.name + " is deprecated. Please use " + + replacement + " instead"); + }, + + createError: function(msg) { + return new Error("Error in Rangy " + this.name + " module: " + msg); + } + }; + + function createModule(name, dependencies, initFunc) { + var newModule = new Module(name, dependencies, function(module) { + if (!module.initialized) { + module.initialized = true; + try { + initFunc(api, module); + module.supported = true; + } catch (ex) { + var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex); + consoleLog(errorMessage); + if (ex.stack) { + consoleLog(ex.stack); + } + } + } + }); + modules[name] = newModule; + return newModule; + } + + api.createModule = function(name) { + // Allow 2 or 3 arguments (second argument is an optional array of dependencies) + var initFunc, dependencies; + if (arguments.length == 2) { + initFunc = arguments[1]; + dependencies = []; + } else { + initFunc = arguments[2]; + dependencies = arguments[1]; + } + + var module = createModule(name, dependencies, initFunc); + + // Initialize the module immediately if the core is already initialized + if (api.initialized && api.supported) { + module.init(); + } + }; + + api.createCoreModule = function(name, dependencies, initFunc) { + createModule(name, dependencies, initFunc); + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately + + function RangePrototype() {} + api.RangePrototype = RangePrototype; + api.rangePrototype = new RangePrototype(); + + function SelectionPrototype() {} + api.selectionPrototype = new SelectionPrototype(); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // DOM utility methods used by Rangy + api.createCoreModule("DomUtil", [], function(api, module) { + var UNDEF = "undefined"; + var util = api.util; + var getBody = util.getBody; + + // Perform feature tests + if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { + module.fail("document missing a Node creation method"); + } + + if (!util.isHostMethod(document, "getElementsByTagName")) { + module.fail("document missing getElementsByTagName method"); + } + + var el = document.createElement("div"); + if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || + !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { + module.fail("Incomplete Element implementation"); + } + + // innerHTML is required for Range's createContextualFragment method + if (!util.isHostProperty(el, "innerHTML")) { + module.fail("Element is missing innerHTML property"); + } + + var textNode = document.createTextNode("test"); + if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || + !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || + !util.areHostProperties(textNode, ["data"]))) { + module.fail("Incomplete Text Node implementation"); + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been + // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that + // contains just the document as a single element and the value searched for is the document. + var arrayContains = /*Array.prototype.indexOf ? + function(arr, val) { + return arr.indexOf(val) > -1; + }:*/ + + function(arr, val) { + var i = arr.length; + while (i--) { + if (arr[i] === val) { + return true; + } + } + return false; + }; + + // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI + function isHtmlNamespace(node) { + var ns; + return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); + } + + function parentElement(node) { + var parent = node.parentNode; + return (parent.nodeType == 1) ? parent : null; + } + + function getNodeIndex(node) { + var i = 0; + while( (node = node.previousSibling) ) { + ++i; + } + return i; + } + + function getNodeLength(node) { + switch (node.nodeType) { + case 7: + case 10: + return 0; + case 3: + case 8: + return node.length; + default: + return node.childNodes.length; + } + } + + function getCommonAncestor(node1, node2) { + var ancestors = [], n; + for (n = node1; n; n = n.parentNode) { + ancestors.push(n); + } + + for (n = node2; n; n = n.parentNode) { + if (arrayContains(ancestors, n)) { + return n; + } + } + + return null; + } + + function isAncestorOf(ancestor, descendant, selfIsAncestor) { + var n = selfIsAncestor ? descendant : descendant.parentNode; + while (n) { + if (n === ancestor) { + return true; + } else { + n = n.parentNode; + } + } + return false; + } + + function isOrIsAncestorOf(ancestor, descendant) { + return isAncestorOf(ancestor, descendant, true); + } + + function getClosestAncestorIn(node, ancestor, selfIsAncestor) { + var p, n = selfIsAncestor ? node : node.parentNode; + while (n) { + p = n.parentNode; + if (p === ancestor) { + return n; + } + n = p; + } + return null; + } + + function isCharacterDataNode(node) { + var t = node.nodeType; + return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment + } + + function isTextOrCommentNode(node) { + if (!node) { + return false; + } + var t = node.nodeType; + return t == 3 || t == 8 ; // Text or Comment + } + + function insertAfter(node, precedingNode) { + var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; + if (nextNode) { + parent.insertBefore(node, nextNode); + } else { + parent.appendChild(node); + } + return node; + } + + // Note that we cannot use splitText() because it is bugridden in IE 9. + function splitDataNode(node, index, positionsToPreserve) { + var newNode = node.cloneNode(false); + newNode.deleteData(0, index); + node.deleteData(index, node.length - index); + insertAfter(newNode, node); + + // Preserve positions + if (positionsToPreserve) { + for (var i = 0, position; position = positionsToPreserve[i++]; ) { + // Handle case where position was inside the portion of node after the split point + if (position.node == node && position.offset > index) { + position.node = newNode; + position.offset -= index; + } + // Handle the case where the position is a node offset within node's parent + else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) { + ++position.offset; + } + } + } + return newNode; + } + + function getDocument(node) { + if (node.nodeType == 9) { + return node; + } else if (typeof node.ownerDocument != UNDEF) { + return node.ownerDocument; + } else if (typeof node.document != UNDEF) { + return node.document; + } else if (node.parentNode) { + return getDocument(node.parentNode); + } else { + throw module.createError("getDocument: no document found for node"); + } + } + + function getWindow(node) { + var doc = getDocument(node); + if (typeof doc.defaultView != UNDEF) { + return doc.defaultView; + } else if (typeof doc.parentWindow != UNDEF) { + return doc.parentWindow; + } else { + throw module.createError("Cannot get a window object for node"); + } + } + + function getIframeDocument(iframeEl) { + if (typeof iframeEl.contentDocument != UNDEF) { + return iframeEl.contentDocument; + } else if (typeof iframeEl.contentWindow != UNDEF) { + return iframeEl.contentWindow.document; + } else { + throw module.createError("getIframeDocument: No Document object found for iframe element"); + } + } + + function getIframeWindow(iframeEl) { + if (typeof iframeEl.contentWindow != UNDEF) { + return iframeEl.contentWindow; + } else if (typeof iframeEl.contentDocument != UNDEF) { + return iframeEl.contentDocument.defaultView; + } else { + throw module.createError("getIframeWindow: No Window object found for iframe element"); + } + } + + // This looks bad. Is it worth it? + function isWindow(obj) { + return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document"); + } + + function getContentDocument(obj, module, methodName) { + var doc; + + if (!obj) { + doc = document; + } + + // Test if a DOM node has been passed and obtain a document object for it if so + else if (util.isHostProperty(obj, "nodeType")) { + doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") ? + getIframeDocument(obj) : getDocument(obj); + } + + // Test if the doc parameter appears to be a Window object + else if (isWindow(obj)) { + doc = obj.document; + } + + if (!doc) { + throw module.createError(methodName + "(): Parameter must be a Window object or DOM node"); + } + + return doc; + } + + function getRootContainer(node) { + var parent; + while ( (parent = node.parentNode) ) { + node = parent; + } + return node; + } + + function comparePoints(nodeA, offsetA, nodeB, offsetB) { + // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing + var nodeC, root, childA, childB, n; + if (nodeA == nodeB) { + // Case 1: nodes are the same + return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; + } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { + // Case 2: node C (container B or an ancestor) is a child node of A + return offsetA <= getNodeIndex(nodeC) ? -1 : 1; + } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { + // Case 3: node C (container A or an ancestor) is a child node of B + return getNodeIndex(nodeC) < offsetB ? -1 : 1; + } else { + root = getCommonAncestor(nodeA, nodeB); + if (!root) { + throw new Error("comparePoints error: nodes have no common ancestor"); + } + + // Case 4: containers are siblings or descendants of siblings + childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); + childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); + + if (childA === childB) { + // This shouldn't be possible + throw module.createError("comparePoints got to case 4 and childA and childB are the same!"); + } else { + n = root.firstChild; + while (n) { + if (n === childA) { + return -1; + } else if (n === childB) { + return 1; + } + n = n.nextSibling; + } + } + } + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried + var crashyTextNodes = false; + + function isBrokenNode(node) { + var n; + try { + n = node.parentNode; + return false; + } catch (e) { + return true; + } + } + + (function() { + var el = document.createElement("b"); + el.innerHTML = "1"; + var textNode = el.firstChild; + el.innerHTML = "<br />"; + crashyTextNodes = isBrokenNode(textNode); + + api.features.crashyTextNodes = crashyTextNodes; + })(); + + /*----------------------------------------------------------------------------------------------------------------*/ + + function inspectNode(node) { + if (!node) { + return "[No node]"; + } + if (crashyTextNodes && isBrokenNode(node)) { + return "[Broken node]"; + } + if (isCharacterDataNode(node)) { + return '"' + node.data + '"'; + } + if (node.nodeType == 1) { + var idAttr = node.id ? ' id="' + node.id + '"' : ""; + return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]"; + } + return node.nodeName; + } + + function fragmentFromNodeChildren(node) { + var fragment = getDocument(node).createDocumentFragment(), child; + while ( (child = node.firstChild) ) { + fragment.appendChild(child); + } + return fragment; + } + + var getComputedStyleProperty; + if (typeof window.getComputedStyle != UNDEF) { + getComputedStyleProperty = function(el, propName) { + return getWindow(el).getComputedStyle(el, null)[propName]; + }; + } else if (typeof document.documentElement.currentStyle != UNDEF) { + getComputedStyleProperty = function(el, propName) { + return el.currentStyle ? el.currentStyle[propName] : ""; + }; + } else { + module.fail("No means of obtaining computed style properties found"); + } + + function createTestElement(doc, html, contentEditable) { + var body = getBody(doc); + var el = doc.createElement("div"); + el.contentEditable = "" + !!contentEditable; + if (html) { + el.innerHTML = html; + } + + // Insert the test element at the start of the body to prevent scrolling to the bottom in iOS (issue #292) + var bodyFirstChild = body.firstChild; + if (bodyFirstChild) { + body.insertBefore(el, bodyFirstChild); + } else { + body.appendChild(el); + } + + return el; + } + + function removeNode(node) { + return node.parentNode.removeChild(node); + } + + function NodeIterator(root) { + this.root = root; + this._next = root; + } + + NodeIterator.prototype = { + _current: null, + + hasNext: function() { + return !!this._next; + }, + + next: function() { + var n = this._current = this._next; + var child, next; + if (this._current) { + child = n.firstChild; + if (child) { + this._next = child; + } else { + next = null; + while ((n !== this.root) && !(next = n.nextSibling)) { + n = n.parentNode; + } + this._next = next; + } + } + return this._current; + }, + + detach: function() { + this._current = this._next = this.root = null; + } + }; + + function createIterator(root) { + return new NodeIterator(root); + } + + function DomPosition(node, offset) { + this.node = node; + this.offset = offset; + } + + DomPosition.prototype = { + equals: function(pos) { + return !!pos && this.node === pos.node && this.offset == pos.offset; + }, + + inspect: function() { + return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; + }, + + toString: function() { + return this.inspect(); + } + }; + + function DOMException(codeName) { + this.code = this[codeName]; + this.codeName = codeName; + this.message = "DOMException: " + this.codeName; + } + + DOMException.prototype = { + INDEX_SIZE_ERR: 1, + HIERARCHY_REQUEST_ERR: 3, + WRONG_DOCUMENT_ERR: 4, + NO_MODIFICATION_ALLOWED_ERR: 7, + NOT_FOUND_ERR: 8, + NOT_SUPPORTED_ERR: 9, + INVALID_STATE_ERR: 11, + INVALID_NODE_TYPE_ERR: 24 + }; + + DOMException.prototype.toString = function() { + return this.message; + }; + + api.dom = { + arrayContains: arrayContains, + isHtmlNamespace: isHtmlNamespace, + parentElement: parentElement, + getNodeIndex: getNodeIndex, + getNodeLength: getNodeLength, + getCommonAncestor: getCommonAncestor, + isAncestorOf: isAncestorOf, + isOrIsAncestorOf: isOrIsAncestorOf, + getClosestAncestorIn: getClosestAncestorIn, + isCharacterDataNode: isCharacterDataNode, + isTextOrCommentNode: isTextOrCommentNode, + insertAfter: insertAfter, + splitDataNode: splitDataNode, + getDocument: getDocument, + getWindow: getWindow, + getIframeWindow: getIframeWindow, + getIframeDocument: getIframeDocument, + getBody: getBody, + isWindow: isWindow, + getContentDocument: getContentDocument, + getRootContainer: getRootContainer, + comparePoints: comparePoints, + isBrokenNode: isBrokenNode, + inspectNode: inspectNode, + getComputedStyleProperty: getComputedStyleProperty, + createTestElement: createTestElement, + removeNode: removeNode, + fragmentFromNodeChildren: fragmentFromNodeChildren, + createIterator: createIterator, + DomPosition: DomPosition + }; + + api.DOMException = DOMException; + }); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Pure JavaScript implementation of DOM Range + api.createCoreModule("DomRange", ["DomUtil"], function(api, module) { + var dom = api.dom; + var util = api.util; + var DomPosition = dom.DomPosition; + var DOMException = api.DOMException; + + var isCharacterDataNode = dom.isCharacterDataNode; + var getNodeIndex = dom.getNodeIndex; + var isOrIsAncestorOf = dom.isOrIsAncestorOf; + var getDocument = dom.getDocument; + var comparePoints = dom.comparePoints; + var splitDataNode = dom.splitDataNode; + var getClosestAncestorIn = dom.getClosestAncestorIn; + var getNodeLength = dom.getNodeLength; + var arrayContains = dom.arrayContains; + var getRootContainer = dom.getRootContainer; + var crashyTextNodes = api.features.crashyTextNodes; + + var removeNode = dom.removeNode; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Utility functions + + function isNonTextPartiallySelected(node, range) { + return (node.nodeType != 3) && + (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer)); + } + + function getRangeDocument(range) { + return range.document || getDocument(range.startContainer); + } + + function getRangeRoot(range) { + return getRootContainer(range.startContainer); + } + + function getBoundaryBeforeNode(node) { + return new DomPosition(node.parentNode, getNodeIndex(node)); + } + + function getBoundaryAfterNode(node) { + return new DomPosition(node.parentNode, getNodeIndex(node) + 1); + } + + function insertNodeAtPosition(node, n, o) { + var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; + if (isCharacterDataNode(n)) { + if (o == n.length) { + dom.insertAfter(node, n); + } else { + n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o)); + } + } else if (o >= n.childNodes.length) { + n.appendChild(node); + } else { + n.insertBefore(node, n.childNodes[o]); + } + return firstNodeInserted; + } + + function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) { + assertRangeValid(rangeA); + assertRangeValid(rangeB); + + if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) { + throw new DOMException("WRONG_DOCUMENT_ERR"); + } + + var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset), + endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset); + + return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; + } + + function cloneSubtree(iterator) { + var partiallySelected; + for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { + partiallySelected = iterator.isPartiallySelectedSubtree(); + node = node.cloneNode(!partiallySelected); + if (partiallySelected) { + subIterator = iterator.getSubtreeIterator(); + node.appendChild(cloneSubtree(subIterator)); + subIterator.detach(); + } + + if (node.nodeType == 10) { // DocumentType + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + frag.appendChild(node); + } + return frag; + } + + function iterateSubtree(rangeIterator, func, iteratorState) { + var it, n; + iteratorState = iteratorState || { stop: false }; + for (var node, subRangeIterator; node = rangeIterator.next(); ) { + if (rangeIterator.isPartiallySelectedSubtree()) { + if (func(node) === false) { + iteratorState.stop = true; + return; + } else { + // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of + // the node selected by the Range. + subRangeIterator = rangeIterator.getSubtreeIterator(); + iterateSubtree(subRangeIterator, func, iteratorState); + subRangeIterator.detach(); + if (iteratorState.stop) { + return; + } + } + } else { + // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its + // descendants + it = dom.createIterator(node); + while ( (n = it.next()) ) { + if (func(n) === false) { + iteratorState.stop = true; + return; + } + } + } + } + } + + function deleteSubtree(iterator) { + var subIterator; + while (iterator.next()) { + if (iterator.isPartiallySelectedSubtree()) { + subIterator = iterator.getSubtreeIterator(); + deleteSubtree(subIterator); + subIterator.detach(); + } else { + iterator.remove(); + } + } + } + + function extractSubtree(iterator) { + for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { + + if (iterator.isPartiallySelectedSubtree()) { + node = node.cloneNode(false); + subIterator = iterator.getSubtreeIterator(); + node.appendChild(extractSubtree(subIterator)); + subIterator.detach(); + } else { + iterator.remove(); + } + if (node.nodeType == 10) { // DocumentType + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + frag.appendChild(node); + } + return frag; + } + + function getNodesInRange(range, nodeTypes, filter) { + var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; + var filterExists = !!filter; + if (filterNodeTypes) { + regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); + } + + var nodes = []; + iterateSubtree(new RangeIterator(range, false), function(node) { + if (filterNodeTypes && !regex.test(node.nodeType)) { + return; + } + if (filterExists && !filter(node)) { + return; + } + // Don't include a boundary container if it is a character data node and the range does not contain any + // of its character data. See issue 190. + var sc = range.startContainer; + if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) { + return; + } + + var ec = range.endContainer; + if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) { + return; + } + + nodes.push(node); + }); + return nodes; + } + + function inspect(range) { + var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); + return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + + dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) + + function RangeIterator(range, clonePartiallySelectedTextNodes) { + this.range = range; + this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; + + + if (!range.collapsed) { + this.sc = range.startContainer; + this.so = range.startOffset; + this.ec = range.endContainer; + this.eo = range.endOffset; + var root = range.commonAncestorContainer; + + if (this.sc === this.ec && isCharacterDataNode(this.sc)) { + this.isSingleCharacterDataNode = true; + this._first = this._last = this._next = this.sc; + } else { + this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ? + this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true); + this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ? + this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true); + } + } + } + + RangeIterator.prototype = { + _current: null, + _next: null, + _first: null, + _last: null, + isSingleCharacterDataNode: false, + + reset: function() { + this._current = null; + this._next = this._first; + }, + + hasNext: function() { + return !!this._next; + }, + + next: function() { + // Move to next node + var current = this._current = this._next; + if (current) { + this._next = (current !== this._last) ? current.nextSibling : null; + + // Check for partially selected text nodes + if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { + if (current === this.ec) { + (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); + } + if (this._current === this.sc) { + (current = current.cloneNode(true)).deleteData(0, this.so); + } + } + } + + return current; + }, + + remove: function() { + var current = this._current, start, end; + + if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { + start = (current === this.sc) ? this.so : 0; + end = (current === this.ec) ? this.eo : current.length; + if (start != end) { + current.deleteData(start, end - start); + } + } else { + if (current.parentNode) { + removeNode(current); + } else { + } + } + }, + + // Checks if the current node is partially selected + isPartiallySelectedSubtree: function() { + var current = this._current; + return isNonTextPartiallySelected(current, this.range); + }, + + getSubtreeIterator: function() { + var subRange; + if (this.isSingleCharacterDataNode) { + subRange = this.range.cloneRange(); + subRange.collapse(false); + } else { + subRange = new Range(getRangeDocument(this.range)); + var current = this._current; + var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current); + + if (isOrIsAncestorOf(current, this.sc)) { + startContainer = this.sc; + startOffset = this.so; + } + if (isOrIsAncestorOf(current, this.ec)) { + endContainer = this.ec; + endOffset = this.eo; + } + + updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); + } + return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); + }, + + detach: function() { + this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; + } + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; + var rootContainerNodeTypes = [2, 9, 11]; + var readonlyNodeTypes = [5, 6, 10, 12]; + var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; + var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; + + function createAncestorFinder(nodeTypes) { + return function(node, selfIsAncestor) { + var t, n = selfIsAncestor ? node : node.parentNode; + while (n) { + t = n.nodeType; + if (arrayContains(nodeTypes, t)) { + return n; + } + n = n.parentNode; + } + return null; + }; + } + + var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); + var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); + var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); + + function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { + if (getDocTypeNotationEntityAncestor(node, allowSelf)) { + throw new DOMException("INVALID_NODE_TYPE_ERR"); + } + } + + function assertValidNodeType(node, invalidTypes) { + if (!arrayContains(invalidTypes, node.nodeType)) { + throw new DOMException("INVALID_NODE_TYPE_ERR"); + } + } + + function assertValidOffset(node, offset) { + if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) { + throw new DOMException("INDEX_SIZE_ERR"); + } + } + + function assertSameDocumentOrFragment(node1, node2) { + if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { + throw new DOMException("WRONG_DOCUMENT_ERR"); + } + } + + function assertNodeNotReadOnly(node) { + if (getReadonlyAncestor(node, true)) { + throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); + } + } + + function assertNode(node, codeName) { + if (!node) { + throw new DOMException(codeName); + } + } + + function isValidOffset(node, offset) { + return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length); + } + + function isRangeValid(range) { + return (!!range.startContainer && !!range.endContainer && + !(crashyTextNodes && (dom.isBrokenNode(range.startContainer) || dom.isBrokenNode(range.endContainer))) && + getRootContainer(range.startContainer) == getRootContainer(range.endContainer) && + isValidOffset(range.startContainer, range.startOffset) && + isValidOffset(range.endContainer, range.endOffset)); + } + + function assertRangeValid(range) { + if (!isRangeValid(range)) { + throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: (" + range.inspect() + ")"); + } + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Test the browser's innerHTML support to decide how to implement createContextualFragment + var styleEl = document.createElement("style"); + var htmlParsingConforms = false; + try { + styleEl.innerHTML = "<b>x</b>"; + htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node + } catch (e) { + // IE 6 and 7 throw + } + + api.features.htmlParsingConforms = htmlParsingConforms; + + var createContextualFragment = htmlParsingConforms ? + + // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See + // discussion and base code for this implementation at issue 67. + // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface + // Thanks to Aleks Williams. + function(fragmentStr) { + // "Let node the context object's start's node." + var node = this.startContainer; + var doc = getDocument(node); + + // "If the context object's start's node is null, raise an INVALID_STATE_ERR + // exception and abort these steps." + if (!node) { + throw new DOMException("INVALID_STATE_ERR"); + } + + // "Let element be as follows, depending on node's interface:" + // Document, Document Fragment: null + var el = null; + + // "Element: node" + if (node.nodeType == 1) { + el = node; + + // "Text, Comment: node's parentElement" + } else if (isCharacterDataNode(node)) { + el = dom.parentElement(node); + } + + // "If either element is null or element's ownerDocument is an HTML document + // and element's local name is "html" and element's namespace is the HTML + // namespace" + if (el === null || ( + el.nodeName == "HTML" && + dom.isHtmlNamespace(getDocument(el).documentElement) && + dom.isHtmlNamespace(el) + )) { + + // "let element be a new Element with "body" as its local name and the HTML + // namespace as its namespace."" + el = doc.createElement("body"); + } else { + el = el.cloneNode(false); + } + + // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." + // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." + // "In either case, the algorithm must be invoked with fragment as the input + // and element as the context element." + el.innerHTML = fragmentStr; + + // "If this raises an exception, then abort these steps. Otherwise, let new + // children be the nodes returned." + + // "Let fragment be a new DocumentFragment." + // "Append all new children to fragment." + // "Return fragment." + return dom.fragmentFromNodeChildren(el); + } : + + // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that + // previous versions of Rangy used (with the exception of using a body element rather than a div) + function(fragmentStr) { + var doc = getRangeDocument(this); + var el = doc.createElement("body"); + el.innerHTML = fragmentStr; + + return dom.fragmentFromNodeChildren(el); + }; + + function splitRangeBoundaries(range, positionsToPreserve) { + assertRangeValid(range); + + var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset; + var startEndSame = (sc === ec); + + if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { + splitDataNode(ec, eo, positionsToPreserve); + } + + if (isCharacterDataNode(sc) && so > 0 && so < sc.length) { + sc = splitDataNode(sc, so, positionsToPreserve); + if (startEndSame) { + eo -= so; + ec = sc; + } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) { + eo++; + } + so = 0; + } + range.setStartAndEnd(sc, so, ec, eo); + } + + function rangeToHtml(range) { + assertRangeValid(range); + var container = range.commonAncestorContainer.parentNode.cloneNode(false); + container.appendChild( range.cloneContents() ); + return container.innerHTML; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", + "commonAncestorContainer"]; + + var s2s = 0, s2e = 1, e2e = 2, e2s = 3; + var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; + + util.extend(api.rangePrototype, { + compareBoundaryPoints: function(how, range) { + assertRangeValid(this); + assertSameDocumentOrFragment(this.startContainer, range.startContainer); + + var nodeA, offsetA, nodeB, offsetB; + var prefixA = (how == e2s || how == s2s) ? "start" : "end"; + var prefixB = (how == s2e || how == s2s) ? "start" : "end"; + nodeA = this[prefixA + "Container"]; + offsetA = this[prefixA + "Offset"]; + nodeB = range[prefixB + "Container"]; + offsetB = range[prefixB + "Offset"]; + return comparePoints(nodeA, offsetA, nodeB, offsetB); + }, + + insertNode: function(node) { + assertRangeValid(this); + assertValidNodeType(node, insertableNodeTypes); + assertNodeNotReadOnly(this.startContainer); + + if (isOrIsAncestorOf(node, this.startContainer)) { + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + + // No check for whether the container of the start of the Range is of a type that does not allow + // children of the type of node: the browser's DOM implementation should do this for us when we attempt + // to add the node + + var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); + this.setStartBefore(firstNodeInserted); + }, + + cloneContents: function() { + assertRangeValid(this); + + var clone, frag; + if (this.collapsed) { + return getRangeDocument(this).createDocumentFragment(); + } else { + if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) { + clone = this.startContainer.cloneNode(true); + clone.data = clone.data.slice(this.startOffset, this.endOffset); + frag = getRangeDocument(this).createDocumentFragment(); + frag.appendChild(clone); + return frag; + } else { + var iterator = new RangeIterator(this, true); + clone = cloneSubtree(iterator); + iterator.detach(); + } + return clone; + } + }, + + canSurroundContents: function() { + assertRangeValid(this); + assertNodeNotReadOnly(this.startContainer); + assertNodeNotReadOnly(this.endContainer); + + // Check if the contents can be surrounded. Specifically, this means whether the range partially selects + // no non-text nodes. + var iterator = new RangeIterator(this, true); + var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || + (iterator._last && isNonTextPartiallySelected(iterator._last, this))); + iterator.detach(); + return !boundariesInvalid; + }, + + surroundContents: function(node) { + assertValidNodeType(node, surroundNodeTypes); + + if (!this.canSurroundContents()) { + throw new DOMException("INVALID_STATE_ERR"); + } + + // Extract the contents + var content = this.extractContents(); + + // Clear the children of the node + if (node.hasChildNodes()) { + while (node.lastChild) { + node.removeChild(node.lastChild); + } + } + + // Insert the new node and add the extracted contents + insertNodeAtPosition(node, this.startContainer, this.startOffset); + node.appendChild(content); + + this.selectNode(node); + }, + + cloneRange: function() { + assertRangeValid(this); + var range = new Range(getRangeDocument(this)); + var i = rangeProperties.length, prop; + while (i--) { + prop = rangeProperties[i]; + range[prop] = this[prop]; + } + return range; + }, + + toString: function() { + assertRangeValid(this); + var sc = this.startContainer; + if (sc === this.endContainer && isCharacterDataNode(sc)) { + return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; + } else { + var textParts = [], iterator = new RangeIterator(this, true); + iterateSubtree(iterator, function(node) { + // Accept only text or CDATA nodes, not comments + if (node.nodeType == 3 || node.nodeType == 4) { + textParts.push(node.data); + } + }); + iterator.detach(); + return textParts.join(""); + } + }, + + // The methods below are all non-standard. The following batch were introduced by Mozilla but have since + // been removed from Mozilla. + + compareNode: function(node) { + assertRangeValid(this); + + var parent = node.parentNode; + var nodeIndex = getNodeIndex(node); + + if (!parent) { + throw new DOMException("NOT_FOUND_ERR"); + } + + var startComparison = this.comparePoint(parent, nodeIndex), + endComparison = this.comparePoint(parent, nodeIndex + 1); + + if (startComparison < 0) { // Node starts before + return (endComparison > 0) ? n_b_a : n_b; + } else { + return (endComparison > 0) ? n_a : n_i; + } + }, + + comparePoint: function(node, offset) { + assertRangeValid(this); + assertNode(node, "HIERARCHY_REQUEST_ERR"); + assertSameDocumentOrFragment(node, this.startContainer); + + if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { + return -1; + } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { + return 1; + } + return 0; + }, + + createContextualFragment: createContextualFragment, + + toHtml: function() { + return rangeToHtml(this); + }, + + // touchingIsIntersecting determines whether this method considers a node that borders a range intersects + // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) + intersectsNode: function(node, touchingIsIntersecting) { + assertRangeValid(this); + if (getRootContainer(node) != getRangeRoot(this)) { + return false; + } + + var parent = node.parentNode, offset = getNodeIndex(node); + if (!parent) { + return true; + } + + var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset), + endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset); + + return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; + }, + + isPointInRange: function(node, offset) { + assertRangeValid(this); + assertNode(node, "HIERARCHY_REQUEST_ERR"); + assertSameDocumentOrFragment(node, this.startContainer); + + return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && + (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); + }, + + // The methods below are non-standard and invented by me. + + // Sharing a boundary start-to-end or end-to-start does not count as intersection. + intersectsRange: function(range) { + return rangesIntersect(this, range, false); + }, + + // Sharing a boundary start-to-end or end-to-start does count as intersection. + intersectsOrTouchesRange: function(range) { + return rangesIntersect(this, range, true); + }, + + intersection: function(range) { + if (this.intersectsRange(range)) { + var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), + endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); + + var intersectionRange = this.cloneRange(); + if (startComparison == -1) { + intersectionRange.setStart(range.startContainer, range.startOffset); + } + if (endComparison == 1) { + intersectionRange.setEnd(range.endContainer, range.endOffset); + } + return intersectionRange; + } + return null; + }, + + union: function(range) { + if (this.intersectsOrTouchesRange(range)) { + var unionRange = this.cloneRange(); + if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { + unionRange.setStart(range.startContainer, range.startOffset); + } + if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { + unionRange.setEnd(range.endContainer, range.endOffset); + } + return unionRange; + } else { + throw new DOMException("Ranges do not intersect"); + } + }, + + containsNode: function(node, allowPartial) { + if (allowPartial) { + return this.intersectsNode(node, false); + } else { + return this.compareNode(node) == n_i; + } + }, + + containsNodeContents: function(node) { + return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0; + }, + + containsRange: function(range) { + var intersection = this.intersection(range); + return intersection !== null && range.equals(intersection); + }, + + containsNodeText: function(node) { + var nodeRange = this.cloneRange(); + nodeRange.selectNode(node); + var textNodes = nodeRange.getNodes([3]); + if (textNodes.length > 0) { + nodeRange.setStart(textNodes[0], 0); + var lastTextNode = textNodes.pop(); + nodeRange.setEnd(lastTextNode, lastTextNode.length); + return this.containsRange(nodeRange); + } else { + return this.containsNodeContents(node); + } + }, + + getNodes: function(nodeTypes, filter) { + assertRangeValid(this); + return getNodesInRange(this, nodeTypes, filter); + }, + + getDocument: function() { + return getRangeDocument(this); + }, + + collapseBefore: function(node) { + this.setEndBefore(node); + this.collapse(false); + }, + + collapseAfter: function(node) { + this.setStartAfter(node); + this.collapse(true); + }, + + getBookmark: function(containerNode) { + var doc = getRangeDocument(this); + var preSelectionRange = api.createRange(doc); + containerNode = containerNode || dom.getBody(doc); + preSelectionRange.selectNodeContents(containerNode); + var range = this.intersection(preSelectionRange); + var start = 0, end = 0; + if (range) { + preSelectionRange.setEnd(range.startContainer, range.startOffset); + start = preSelectionRange.toString().length; + end = start + range.toString().length; + } + + return { + start: start, + end: end, + containerNode: containerNode + }; + }, + + moveToBookmark: function(bookmark) { + var containerNode = bookmark.containerNode; + var charIndex = 0; + this.setStart(containerNode, 0); + this.collapse(true); + var nodeStack = [containerNode], node, foundStart = false, stop = false; + var nextCharIndex, i, childNodes; + + while (!stop && (node = nodeStack.pop())) { + if (node.nodeType == 3) { + nextCharIndex = charIndex + node.length; + if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) { + this.setStart(node, bookmark.start - charIndex); + foundStart = true; + } + if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) { + this.setEnd(node, bookmark.end - charIndex); + stop = true; + } + charIndex = nextCharIndex; + } else { + childNodes = node.childNodes; + i = childNodes.length; + while (i--) { + nodeStack.push(childNodes[i]); + } + } + } + }, + + getName: function() { + return "DomRange"; + }, + + equals: function(range) { + return Range.rangesEqual(this, range); + }, + + isValid: function() { + return isRangeValid(this); + }, + + inspect: function() { + return inspect(this); + }, + + detach: function() { + // In DOM4, detach() is now a no-op. + } + }); + + function copyComparisonConstantsToObject(obj) { + obj.START_TO_START = s2s; + obj.START_TO_END = s2e; + obj.END_TO_END = e2e; + obj.END_TO_START = e2s; + + obj.NODE_BEFORE = n_b; + obj.NODE_AFTER = n_a; + obj.NODE_BEFORE_AND_AFTER = n_b_a; + obj.NODE_INSIDE = n_i; + } + + function copyComparisonConstants(constructor) { + copyComparisonConstantsToObject(constructor); + copyComparisonConstantsToObject(constructor.prototype); + } + + function createRangeContentRemover(remover, boundaryUpdater) { + return function() { + assertRangeValid(this); + + var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; + + var iterator = new RangeIterator(this, true); + + // Work out where to position the range after content removal + var node, boundary; + if (sc !== root) { + node = getClosestAncestorIn(sc, root, true); + boundary = getBoundaryAfterNode(node); + sc = boundary.node; + so = boundary.offset; + } + + // Check none of the range is read-only + iterateSubtree(iterator, assertNodeNotReadOnly); + + iterator.reset(); + + // Remove the content + var returnValue = remover(iterator); + iterator.detach(); + + // Move to the new position + boundaryUpdater(this, sc, so, sc, so); + + return returnValue; + }; + } + + function createPrototypeRange(constructor, boundaryUpdater) { + function createBeforeAfterNodeSetter(isBefore, isStart) { + return function(node) { + assertValidNodeType(node, beforeAfterNodeTypes); + assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); + + var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); + (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); + }; + } + + function setRangeStart(range, node, offset) { + var ec = range.endContainer, eo = range.endOffset; + if (node !== range.startContainer || offset !== range.startOffset) { + // Check the root containers of the range and the new boundary, and also check whether the new boundary + // is after the current end. In either case, collapse the range to the new position + if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) { + ec = node; + eo = offset; + } + boundaryUpdater(range, node, offset, ec, eo); + } + } + + function setRangeEnd(range, node, offset) { + var sc = range.startContainer, so = range.startOffset; + if (node !== range.endContainer || offset !== range.endOffset) { + // Check the root containers of the range and the new boundary, and also check whether the new boundary + // is after the current end. In either case, collapse the range to the new position + if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) { + sc = node; + so = offset; + } + boundaryUpdater(range, sc, so, node, offset); + } + } + + // Set up inheritance + var F = function() {}; + F.prototype = api.rangePrototype; + constructor.prototype = new F(); + + util.extend(constructor.prototype, { + setStart: function(node, offset) { + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + + setRangeStart(this, node, offset); + }, + + setEnd: function(node, offset) { + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + + setRangeEnd(this, node, offset); + }, + + /** + * Convenience method to set a range's start and end boundaries. Overloaded as follows: + * - Two parameters (node, offset) creates a collapsed range at that position + * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at + * startOffset and ending at endOffset + * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in + * startNode and ending at endOffset in endNode + */ + setStartAndEnd: function() { + var args = arguments; + var sc = args[0], so = args[1], ec = sc, eo = so; + + switch (args.length) { + case 3: + eo = args[2]; + break; + case 4: + ec = args[2]; + eo = args[3]; + break; + } + + boundaryUpdater(this, sc, so, ec, eo); + }, + + setBoundary: function(node, offset, isStart) { + this["set" + (isStart ? "Start" : "End")](node, offset); + }, + + setStartBefore: createBeforeAfterNodeSetter(true, true), + setStartAfter: createBeforeAfterNodeSetter(false, true), + setEndBefore: createBeforeAfterNodeSetter(true, false), + setEndAfter: createBeforeAfterNodeSetter(false, false), + + collapse: function(isStart) { + assertRangeValid(this); + if (isStart) { + boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); + } else { + boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); + } + }, + + selectNodeContents: function(node) { + assertNoDocTypeNotationEntityAncestor(node, true); + + boundaryUpdater(this, node, 0, node, getNodeLength(node)); + }, + + selectNode: function(node) { + assertNoDocTypeNotationEntityAncestor(node, false); + assertValidNodeType(node, beforeAfterNodeTypes); + + var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); + boundaryUpdater(this, start.node, start.offset, end.node, end.offset); + }, + + extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), + + deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), + + canSurroundContents: function() { + assertRangeValid(this); + assertNodeNotReadOnly(this.startContainer); + assertNodeNotReadOnly(this.endContainer); + + // Check if the contents can be surrounded. Specifically, this means whether the range partially selects + // no non-text nodes. + var iterator = new RangeIterator(this, true); + var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) || + (iterator._last && isNonTextPartiallySelected(iterator._last, this))); + iterator.detach(); + return !boundariesInvalid; + }, + + splitBoundaries: function() { + splitRangeBoundaries(this); + }, + + splitBoundariesPreservingPositions: function(positionsToPreserve) { + splitRangeBoundaries(this, positionsToPreserve); + }, + + normalizeBoundaries: function() { + assertRangeValid(this); + + var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; + + var mergeForward = function(node) { + var sibling = node.nextSibling; + if (sibling && sibling.nodeType == node.nodeType) { + ec = node; + eo = node.length; + node.appendData(sibling.data); + removeNode(sibling); + } + }; + + var mergeBackward = function(node) { + var sibling = node.previousSibling; + if (sibling && sibling.nodeType == node.nodeType) { + sc = node; + var nodeLength = node.length; + so = sibling.length; + node.insertData(0, sibling.data); + removeNode(sibling); + if (sc == ec) { + eo += so; + ec = sc; + } else if (ec == node.parentNode) { + var nodeIndex = getNodeIndex(node); + if (eo == nodeIndex) { + ec = node; + eo = nodeLength; + } else if (eo > nodeIndex) { + eo--; + } + } + } + }; + + var normalizeStart = true; + var sibling; + + if (isCharacterDataNode(ec)) { + if (eo == ec.length) { + mergeForward(ec); + } else if (eo == 0) { + sibling = ec.previousSibling; + if (sibling && sibling.nodeType == ec.nodeType) { + eo = sibling.length; + if (sc == ec) { + normalizeStart = false; + } + sibling.appendData(ec.data); + removeNode(ec); + ec = sibling; + } + } + } else { + if (eo > 0) { + var endNode = ec.childNodes[eo - 1]; + if (endNode && isCharacterDataNode(endNode)) { + mergeForward(endNode); + } + } + normalizeStart = !this.collapsed; + } + + if (normalizeStart) { + if (isCharacterDataNode(sc)) { + if (so == 0) { + mergeBackward(sc); + } else if (so == sc.length) { + sibling = sc.nextSibling; + if (sibling && sibling.nodeType == sc.nodeType) { + if (ec == sibling) { + ec = sc; + eo += sc.length; + } + sc.appendData(sibling.data); + removeNode(sibling); + } + } + } else { + if (so < sc.childNodes.length) { + var startNode = sc.childNodes[so]; + if (startNode && isCharacterDataNode(startNode)) { + mergeBackward(startNode); + } + } + } + } else { + sc = ec; + so = eo; + } + + boundaryUpdater(this, sc, so, ec, eo); + }, + + collapseToPoint: function(node, offset) { + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + this.setStartAndEnd(node, offset); + } + }); + + copyComparisonConstants(constructor); + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Updates commonAncestorContainer and collapsed after boundary change + function updateCollapsedAndCommonAncestor(range) { + range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); + range.commonAncestorContainer = range.collapsed ? + range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); + } + + function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { + range.startContainer = startContainer; + range.startOffset = startOffset; + range.endContainer = endContainer; + range.endOffset = endOffset; + range.document = dom.getDocument(startContainer); + + updateCollapsedAndCommonAncestor(range); + } + + function Range(doc) { + this.startContainer = doc; + this.startOffset = 0; + this.endContainer = doc; + this.endOffset = 0; + this.document = doc; + updateCollapsedAndCommonAncestor(this); + } + + createPrototypeRange(Range, updateBoundaries); + + util.extend(Range, { + rangeProperties: rangeProperties, + RangeIterator: RangeIterator, + copyComparisonConstants: copyComparisonConstants, + createPrototypeRange: createPrototypeRange, + inspect: inspect, + toHtml: rangeToHtml, + getRangeDocument: getRangeDocument, + rangesEqual: function(r1, r2) { + return r1.startContainer === r2.startContainer && + r1.startOffset === r2.startOffset && + r1.endContainer === r2.endContainer && + r1.endOffset === r2.endOffset; + } + }); + + api.DomRange = Range; + }); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Wrappers for the browser's native DOM Range and/or TextRange implementation + api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { + var WrappedRange, WrappedTextRange; + var dom = api.dom; + var util = api.util; + var DomPosition = dom.DomPosition; + var DomRange = api.DomRange; + var getBody = dom.getBody; + var getContentDocument = dom.getContentDocument; + var isCharacterDataNode = dom.isCharacterDataNode; + + + /*----------------------------------------------------------------------------------------------------------------*/ + + if (api.features.implementsDomRange) { + // This is a wrapper around the browser's native DOM Range. It has two aims: + // - Provide workarounds for specific browser bugs + // - provide convenient extensions, which are inherited from Rangy's DomRange + + (function() { + var rangeProto; + var rangeProperties = DomRange.rangeProperties; + + function updateRangeProperties(range) { + var i = rangeProperties.length, prop; + while (i--) { + prop = rangeProperties[i]; + range[prop] = range.nativeRange[prop]; + } + // Fix for broken collapsed property in IE 9. + range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); + } + + function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) { + var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset); + var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset); + var nativeRangeDifferent = !range.equals(range.nativeRange); + + // Always set both boundaries for the benefit of IE9 (see issue 35) + if (startMoved || endMoved || nativeRangeDifferent) { + range.setEnd(endContainer, endOffset); + range.setStart(startContainer, startOffset); + } + } + + var createBeforeAfterNodeSetter; + + WrappedRange = function(range) { + if (!range) { + throw module.createError("WrappedRange: Range must be specified"); + } + this.nativeRange = range; + updateRangeProperties(this); + }; + + DomRange.createPrototypeRange(WrappedRange, updateNativeRange); + + rangeProto = WrappedRange.prototype; + + rangeProto.selectNode = function(node) { + this.nativeRange.selectNode(node); + updateRangeProperties(this); + }; + + rangeProto.cloneContents = function() { + return this.nativeRange.cloneContents(); + }; + + // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect, + // insertNode() is never delegated to the native range. + + rangeProto.surroundContents = function(node) { + this.nativeRange.surroundContents(node); + updateRangeProperties(this); + }; + + rangeProto.collapse = function(isStart) { + this.nativeRange.collapse(isStart); + updateRangeProperties(this); + }; + + rangeProto.cloneRange = function() { + return new WrappedRange(this.nativeRange.cloneRange()); + }; + + rangeProto.refresh = function() { + updateRangeProperties(this); + }; + + rangeProto.toString = function() { + return this.nativeRange.toString(); + }; + + // Create test range and node for feature detection + + var testTextNode = document.createTextNode("test"); + getBody(document).appendChild(testTextNode); + var range = document.createRange(); + + /*--------------------------------------------------------------------------------------------------------*/ + + // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and + // correct for it + + range.setStart(testTextNode, 0); + range.setEnd(testTextNode, 0); + + try { + range.setStart(testTextNode, 1); + + rangeProto.setStart = function(node, offset) { + this.nativeRange.setStart(node, offset); + updateRangeProperties(this); + }; + + rangeProto.setEnd = function(node, offset) { + this.nativeRange.setEnd(node, offset); + updateRangeProperties(this); + }; + + createBeforeAfterNodeSetter = function(name) { + return function(node) { + this.nativeRange[name](node); + updateRangeProperties(this); + }; + }; + + } catch(ex) { + + rangeProto.setStart = function(node, offset) { + try { + this.nativeRange.setStart(node, offset); + } catch (ex) { + this.nativeRange.setEnd(node, offset); + this.nativeRange.setStart(node, offset); + } + updateRangeProperties(this); + }; + + rangeProto.setEnd = function(node, offset) { + try { + this.nativeRange.setEnd(node, offset); + } catch (ex) { + this.nativeRange.setStart(node, offset); + this.nativeRange.setEnd(node, offset); + } + updateRangeProperties(this); + }; + + createBeforeAfterNodeSetter = function(name, oppositeName) { + return function(node) { + try { + this.nativeRange[name](node); + } catch (ex) { + this.nativeRange[oppositeName](node); + this.nativeRange[name](node); + } + updateRangeProperties(this); + }; + }; + } + + rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore"); + rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter"); + rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore"); + rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter"); + + /*--------------------------------------------------------------------------------------------------------*/ + + // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing + // whether the native implementation can be trusted + rangeProto.selectNodeContents = function(node) { + this.setStartAndEnd(node, 0, dom.getNodeLength(node)); + }; + + /*--------------------------------------------------------------------------------------------------------*/ + + // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for + // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738 + + range.selectNodeContents(testTextNode); + range.setEnd(testTextNode, 3); + + var range2 = document.createRange(); + range2.selectNodeContents(testTextNode); + range2.setEnd(testTextNode, 4); + range2.setStart(testTextNode, 2); + + if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 && + range.compareBoundaryPoints(range.END_TO_START, range2) == 1) { + // This is the wrong way round, so correct for it + + rangeProto.compareBoundaryPoints = function(type, range) { + range = range.nativeRange || range; + if (type == range.START_TO_END) { + type = range.END_TO_START; + } else if (type == range.END_TO_START) { + type = range.START_TO_END; + } + return this.nativeRange.compareBoundaryPoints(type, range); + }; + } else { + rangeProto.compareBoundaryPoints = function(type, range) { + return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range); + }; + } + + /*--------------------------------------------------------------------------------------------------------*/ + + // Test for IE deleteContents() and extractContents() bug and correct it. See issue 107. + + var el = document.createElement("div"); + el.innerHTML = "123"; + var textNode = el.firstChild; + var body = getBody(document); + body.appendChild(el); + + range.setStart(textNode, 1); + range.setEnd(textNode, 2); + range.deleteContents(); + + if (textNode.data == "13") { + // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and + // extractContents() + rangeProto.deleteContents = function() { + this.nativeRange.deleteContents(); + updateRangeProperties(this); + }; + + rangeProto.extractContents = function() { + var frag = this.nativeRange.extractContents(); + updateRangeProperties(this); + return frag; + }; + } else { + } + + body.removeChild(el); + body = null; + + /*--------------------------------------------------------------------------------------------------------*/ + + // Test for existence of createContextualFragment and delegate to it if it exists + if (util.isHostMethod(range, "createContextualFragment")) { + rangeProto.createContextualFragment = function(fragmentStr) { + return this.nativeRange.createContextualFragment(fragmentStr); + }; + } + + /*--------------------------------------------------------------------------------------------------------*/ + + // Clean up + getBody(document).removeChild(testTextNode); + + rangeProto.getName = function() { + return "WrappedRange"; + }; + + api.WrappedRange = WrappedRange; + + api.createNativeRange = function(doc) { + doc = getContentDocument(doc, module, "createNativeRange"); + return doc.createRange(); + }; + })(); + } + + if (api.features.implementsTextRange) { + /* + This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() + method. For example, in the following (where pipes denote the selection boundaries): + + <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul> + + var range = document.selection.createRange(); + alert(range.parentElement().id); // Should alert "ul" but alerts "b" + + This method returns the common ancestor node of the following: + - the parentElement() of the textRange + - the parentElement() of the textRange after calling collapse(true) + - the parentElement() of the textRange after calling collapse(false) + */ + var getTextRangeContainerElement = function(textRange) { + var parentEl = textRange.parentElement(); + var range = textRange.duplicate(); + range.collapse(true); + var startEl = range.parentElement(); + range = textRange.duplicate(); + range.collapse(false); + var endEl = range.parentElement(); + var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); + + return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); + }; + + var textRangeIsCollapsed = function(textRange) { + return textRange.compareEndPoints("StartToEnd", textRange) == 0; + }; + + // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started + // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) + // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange + // bugs, handling for inputs and images, plus optimizations. + var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) { + var workingRange = textRange.duplicate(); + workingRange.collapse(isStart); + var containerElement = workingRange.parentElement(); + + // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so + // check for that + if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) { + containerElement = wholeRangeContainerElement; + } + + + // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and + // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx + if (!containerElement.canHaveHTML) { + var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); + return { + boundaryPosition: pos, + nodeInfo: { + nodeIndex: pos.offset, + containerElement: pos.node + } + }; + } + + var workingNode = dom.getDocument(containerElement).createElement("span"); + + // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5 + // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64 + if (workingNode.parentNode) { + dom.removeNode(workingNode); + } + + var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; + var previousNode, nextNode, boundaryPosition, boundaryNode; + var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0; + var childNodeCount = containerElement.childNodes.length; + var end = childNodeCount; + + // Check end first. Code within the loop assumes that the endth child node of the container is definitely + // after the range boundary. + var nodeIndex = end; + + while (true) { + if (nodeIndex == childNodeCount) { + containerElement.appendChild(workingNode); + } else { + containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]); + } + workingRange.moveToElementText(workingNode); + comparison = workingRange.compareEndPoints(workingComparisonType, textRange); + if (comparison == 0 || start == end) { + break; + } else if (comparison == -1) { + if (end == start + 1) { + // We know the endth child node is after the range boundary, so we must be done. + break; + } else { + start = nodeIndex; + } + } else { + end = (end == start + 1) ? start : nodeIndex; + } + nodeIndex = Math.floor((start + end) / 2); + containerElement.removeChild(workingNode); + } + + + // We've now reached or gone past the boundary of the text range we're interested in + // so have identified the node we want + boundaryNode = workingNode.nextSibling; + + if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) { + // This is a character data node (text, comment, cdata). The working range is collapsed at the start of + // the node containing the text range's boundary, so we move the end of the working range to the + // boundary point and measure the length of its text to get the boundary's offset within the node. + workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); + + var offset; + + if (/[\r\n]/.test(boundaryNode.data)) { + /* + For the particular case of a boundary within a text node containing rendered line breaks (within a + <pre> element, for example), we need a slightly complicated approach to get the boundary's offset in + IE. The facts: + + - Each line break is represented as \r in the text node's data/nodeValue properties + - Each line break is represented as \r\n in the TextRange's 'text' property + - The 'text' property of the TextRange does not contain trailing line breaks + + To get round the problem presented by the final fact above, we can use the fact that TextRange's + moveStart() and moveEnd() methods return the actual number of characters moved, which is not + necessarily the same as the number of characters it was instructed to move. The simplest approach is + to use this to store the characters moved when moving both the start and end of the range to the + start of the document body and subtracting the start offset from the end offset (the + "move-negative-gazillion" method). However, this is extremely slow when the document is large and + the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to + the end of the document) has the same problem. + + Another approach that works is to use moveStart() to move the start boundary of the range up to the + end boundary one character at a time and incrementing a counter with the value returned by the + moveStart() call. However, the check for whether the start boundary has reached the end boundary is + expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected + by the location of the range within the document). + + The approach used below is a hybrid of the two methods above. It uses the fact that a string + containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot + be longer than the text of the TextRange, so the start of the range is moved that length initially + and then a character at a time to make up for any trailing line breaks not contained in the 'text' + property. This has good performance in most situations compared to the previous two methods. + */ + var tempRange = workingRange.duplicate(); + var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length; + + offset = tempRange.moveStart("character", rangeLength); + while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) { + offset++; + tempRange.moveStart("character", 1); + } + } else { + offset = workingRange.text.length; + } + boundaryPosition = new DomPosition(boundaryNode, offset); + } else { + + // If the boundary immediately follows a character data node and this is the end boundary, we should favour + // a position within that, and likewise for a start boundary preceding a character data node + previousNode = (isCollapsed || !isStart) && workingNode.previousSibling; + nextNode = (isCollapsed || isStart) && workingNode.nextSibling; + if (nextNode && isCharacterDataNode(nextNode)) { + boundaryPosition = new DomPosition(nextNode, 0); + } else if (previousNode && isCharacterDataNode(previousNode)) { + boundaryPosition = new DomPosition(previousNode, previousNode.data.length); + } else { + boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode)); + } + } + + // Clean up + dom.removeNode(workingNode); + + return { + boundaryPosition: boundaryPosition, + nodeInfo: { + nodeIndex: nodeIndex, + containerElement: containerElement + } + }; + }; + + // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that + // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange + // (http://code.google.com/p/ierange/) + var createBoundaryTextRange = function(boundaryPosition, isStart) { + var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset; + var doc = dom.getDocument(boundaryPosition.node); + var workingNode, childNodes, workingRange = getBody(doc).createTextRange(); + var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node); + + if (nodeIsDataNode) { + boundaryNode = boundaryPosition.node; + boundaryParent = boundaryNode.parentNode; + } else { + childNodes = boundaryPosition.node.childNodes; + boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null; + boundaryParent = boundaryPosition.node; + } + + // Position the range immediately before the node containing the boundary + workingNode = doc.createElement("span"); + + // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within + // the element rather than immediately before or after it + workingNode.innerHTML = "&#feff;"; + + // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report + // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12 + if (boundaryNode) { + boundaryParent.insertBefore(workingNode, boundaryNode); + } else { + boundaryParent.appendChild(workingNode); + } + + workingRange.moveToElementText(workingNode); + workingRange.collapse(!isStart); + + // Clean up + boundaryParent.removeChild(workingNode); + + // Move the working range to the text offset, if required + if (nodeIsDataNode) { + workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset); + } + + return workingRange; + }; + + /*------------------------------------------------------------------------------------------------------------*/ + + // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a + // prototype + + WrappedTextRange = function(textRange) { + this.textRange = textRange; + this.refresh(); + }; + + WrappedTextRange.prototype = new DomRange(document); + + WrappedTextRange.prototype.refresh = function() { + var start, end, startBoundary; + + // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that. + var rangeContainerElement = getTextRangeContainerElement(this.textRange); + + if (textRangeIsCollapsed(this.textRange)) { + end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, + true).boundaryPosition; + } else { + startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false); + start = startBoundary.boundaryPosition; + + // An optimization used here is that if the start and end boundaries have the same parent element, the + // search scope for the end boundary can be limited to exclude the portion of the element that precedes + // the start boundary + end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false, + startBoundary.nodeInfo).boundaryPosition; + } + + this.setStart(start.node, start.offset); + this.setEnd(end.node, end.offset); + }; + + WrappedTextRange.prototype.getName = function() { + return "WrappedTextRange"; + }; + + DomRange.copyComparisonConstants(WrappedTextRange); + + var rangeToTextRange = function(range) { + if (range.collapsed) { + return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true); + } else { + var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true); + var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false); + var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange(); + textRange.setEndPoint("StartToStart", startRange); + textRange.setEndPoint("EndToEnd", endRange); + return textRange; + } + }; + + WrappedTextRange.rangeToTextRange = rangeToTextRange; + + WrappedTextRange.prototype.toTextRange = function() { + return rangeToTextRange(this); + }; + + api.WrappedTextRange = WrappedTextRange; + + // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which + // implementation to use by default. + if (!api.features.implementsDomRange || api.config.preferTextRange) { + // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work + var globalObj = (function(f) { return f("return this;")(); })(Function); + if (typeof globalObj.Range == "undefined") { + globalObj.Range = WrappedTextRange; + } + + api.createNativeRange = function(doc) { + doc = getContentDocument(doc, module, "createNativeRange"); + return getBody(doc).createTextRange(); + }; + + api.WrappedRange = WrappedTextRange; + } + } + + api.createRange = function(doc) { + doc = getContentDocument(doc, module, "createRange"); + return new api.WrappedRange(api.createNativeRange(doc)); + }; + + api.createRangyRange = function(doc) { + doc = getContentDocument(doc, module, "createRangyRange"); + return new DomRange(doc); + }; + + util.createAliasForDeprecatedMethod(api, "createIframeRange", "createRange"); + util.createAliasForDeprecatedMethod(api, "createIframeRangyRange", "createRangyRange"); + + api.addShimListener(function(win) { + var doc = win.document; + if (typeof doc.createRange == "undefined") { + doc.createRange = function() { + return api.createRange(doc); + }; + } + doc = win = null; + }); + }); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification + // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections) + api.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) { + api.config.checkSelectionRanges = true; + + var BOOLEAN = "boolean"; + var NUMBER = "number"; + var dom = api.dom; + var util = api.util; + var isHostMethod = util.isHostMethod; + var DomRange = api.DomRange; + var WrappedRange = api.WrappedRange; + var DOMException = api.DOMException; + var DomPosition = dom.DomPosition; + var getNativeSelection; + var selectionIsCollapsed; + var features = api.features; + var CONTROL = "Control"; + var getDocument = dom.getDocument; + var getBody = dom.getBody; + var rangesEqual = DomRange.rangesEqual; + + + // Utility function to support direction parameters in the API that may be a string ("backward", "backwards", + // "forward" or "forwards") or a Boolean (true for backwards). + function isDirectionBackward(dir) { + return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir; + } + + function getWindow(win, methodName) { + if (!win) { + return window; + } else if (dom.isWindow(win)) { + return win; + } else if (win instanceof WrappedSelection) { + return win.win; + } else { + var doc = dom.getContentDocument(win, module, methodName); + return dom.getWindow(doc); + } + } + + function getWinSelection(winParam) { + return getWindow(winParam, "getWinSelection").getSelection(); + } + + function getDocSelection(winParam) { + return getWindow(winParam, "getDocSelection").document.selection; + } + + function winSelectionIsBackward(sel) { + var backward = false; + if (sel.anchorNode) { + backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1); + } + return backward; + } + + // Test for the Range/TextRange and Selection features required + // Test for ability to retrieve selection + var implementsWinGetSelection = isHostMethod(window, "getSelection"), + implementsDocSelection = util.isHostObject(document, "selection"); + + features.implementsWinGetSelection = implementsWinGetSelection; + features.implementsDocSelection = implementsDocSelection; + + var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange); + + if (useDocumentSelection) { + getNativeSelection = getDocSelection; + api.isSelectionValid = function(winParam) { + var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection; + + // Check whether the selection TextRange is actually contained within the correct document + return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc); + }; + } else if (implementsWinGetSelection) { + getNativeSelection = getWinSelection; + api.isSelectionValid = function() { + return true; + }; + } else { + module.fail("Neither document.selection or window.getSelection() detected."); + return false; + } + + api.getNativeSelection = getNativeSelection; + + var testSelection = getNativeSelection(); + + // In Firefox, the selection is null in an iframe with display: none. See issue #138. + if (!testSelection) { + module.fail("Native selection was null (possibly issue 138?)"); + return false; + } + + var testRange = api.createNativeRange(document); + var body = getBody(document); + + // Obtaining a range from a selection + var selectionHasAnchorAndFocus = util.areHostProperties(testSelection, + ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]); + + features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus; + + // Test for existence of native selection extend() method + var selectionHasExtend = isHostMethod(testSelection, "extend"); + features.selectionHasExtend = selectionHasExtend; + + // Test if rangeCount exists + var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER); + features.selectionHasRangeCount = selectionHasRangeCount; + + var selectionSupportsMultipleRanges = false; + var collapsedNonEditableSelectionsSupported = true; + + var addRangeBackwardToNative = selectionHasExtend ? + function(nativeSelection, range) { + var doc = DomRange.getRangeDocument(range); + var endRange = api.createRange(doc); + endRange.collapseToPoint(range.endContainer, range.endOffset); + nativeSelection.addRange(getNativeRange(endRange)); + nativeSelection.extend(range.startContainer, range.startOffset); + } : null; + + if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) && + typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) { + + (function() { + // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are + // performed on the current document's selection. See issue 109. + + // Note also that if a selection previously existed, it is wiped and later restored by these tests. This + // will result in the selection direction begin reversed if the original selection was backwards and the + // browser does not support setting backwards selections (Internet Explorer, I'm looking at you). + var sel = window.getSelection(); + if (sel) { + // Store the current selection + var originalSelectionRangeCount = sel.rangeCount; + var selectionHasMultipleRanges = (originalSelectionRangeCount > 1); + var originalSelectionRanges = []; + var originalSelectionBackward = winSelectionIsBackward(sel); + for (var i = 0; i < originalSelectionRangeCount; ++i) { + originalSelectionRanges[i] = sel.getRangeAt(i); + } + + // Create some test elements + var testEl = dom.createTestElement(document, "", false); + var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") ); + + // Test whether the native selection will allow a collapsed selection within a non-editable element + var r1 = document.createRange(); + + r1.setStart(textNode, 1); + r1.collapse(true); + sel.removeAllRanges(); + sel.addRange(r1); + collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1); + sel.removeAllRanges(); + + // Test whether the native selection is capable of supporting multiple ranges. + if (!selectionHasMultipleRanges) { + // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a + // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's + // nothing we can do about this while retaining the feature test so we have to resort to a browser + // sniff. I'm not happy about it. See + // https://code.google.com/p/chromium/issues/detail?id=399791 + var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /); + if (chromeMatch && parseInt(chromeMatch[1]) >= 36) { + selectionSupportsMultipleRanges = false; + } else { + var r2 = r1.cloneRange(); + r1.setStart(textNode, 0); + r2.setEnd(textNode, 3); + r2.setStart(textNode, 2); + sel.addRange(r1); + sel.addRange(r2); + selectionSupportsMultipleRanges = (sel.rangeCount == 2); + } + } + + // Clean up + dom.removeNode(testEl); + sel.removeAllRanges(); + + for (i = 0; i < originalSelectionRangeCount; ++i) { + if (i == 0 && originalSelectionBackward) { + if (addRangeBackwardToNative) { + addRangeBackwardToNative(sel, originalSelectionRanges[i]); + } else { + api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend"); + sel.addRange(originalSelectionRanges[i]); + } + } else { + sel.addRange(originalSelectionRanges[i]); + } + } + } + })(); + } + + features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges; + features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported; + + // ControlRanges + var implementsControlRange = false, testControlRange; + + if (body && isHostMethod(body, "createControlRange")) { + testControlRange = body.createControlRange(); + if (util.areHostProperties(testControlRange, ["item", "add"])) { + implementsControlRange = true; + } + } + features.implementsControlRange = implementsControlRange; + + // Selection collapsedness + if (selectionHasAnchorAndFocus) { + selectionIsCollapsed = function(sel) { + return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset; + }; + } else { + selectionIsCollapsed = function(sel) { + return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false; + }; + } + + function updateAnchorAndFocusFromRange(sel, range, backward) { + var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end"; + sel.anchorNode = range[anchorPrefix + "Container"]; + sel.anchorOffset = range[anchorPrefix + "Offset"]; + sel.focusNode = range[focusPrefix + "Container"]; + sel.focusOffset = range[focusPrefix + "Offset"]; + } + + function updateAnchorAndFocusFromNativeSelection(sel) { + var nativeSel = sel.nativeSelection; + sel.anchorNode = nativeSel.anchorNode; + sel.anchorOffset = nativeSel.anchorOffset; + sel.focusNode = nativeSel.focusNode; + sel.focusOffset = nativeSel.focusOffset; + } + + function updateEmptySelection(sel) { + sel.anchorNode = sel.focusNode = null; + sel.anchorOffset = sel.focusOffset = 0; + sel.rangeCount = 0; + sel.isCollapsed = true; + sel._ranges.length = 0; + } + + function getNativeRange(range) { + var nativeRange; + if (range instanceof DomRange) { + nativeRange = api.createNativeRange(range.getDocument()); + nativeRange.setEnd(range.endContainer, range.endOffset); + nativeRange.setStart(range.startContainer, range.startOffset); + } else if (range instanceof WrappedRange) { + nativeRange = range.nativeRange; + } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) { + nativeRange = range; + } + return nativeRange; + } + + function rangeContainsSingleElement(rangeNodes) { + if (!rangeNodes.length || rangeNodes[0].nodeType != 1) { + return false; + } + for (var i = 1, len = rangeNodes.length; i < len; ++i) { + if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) { + return false; + } + } + return true; + } + + function getSingleElementFromRange(range) { + var nodes = range.getNodes(); + if (!rangeContainsSingleElement(nodes)) { + throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element"); + } + return nodes[0]; + } + + // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange + function isTextRange(range) { + return !!range && typeof range.text != "undefined"; + } + + function updateFromTextRange(sel, range) { + // Create a Range from the selected TextRange + var wrappedRange = new WrappedRange(range); + sel._ranges = [wrappedRange]; + + updateAnchorAndFocusFromRange(sel, wrappedRange, false); + sel.rangeCount = 1; + sel.isCollapsed = wrappedRange.collapsed; + } + + function updateControlSelection(sel) { + // Update the wrapped selection based on what's now in the native selection + sel._ranges.length = 0; + if (sel.docSelection.type == "None") { + updateEmptySelection(sel); + } else { + var controlRange = sel.docSelection.createRange(); + if (isTextRange(controlRange)) { + // This case (where the selection type is "Control" and calling createRange() on the selection returns + // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected + // ControlRange have been removed from the ControlRange and removed from the document. + updateFromTextRange(sel, controlRange); + } else { + sel.rangeCount = controlRange.length; + var range, doc = getDocument(controlRange.item(0)); + for (var i = 0; i < sel.rangeCount; ++i) { + range = api.createRange(doc); + range.selectNode(controlRange.item(i)); + sel._ranges.push(range); + } + sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed; + updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false); + } + } + } + + function addRangeToControlSelection(sel, range) { + var controlRange = sel.docSelection.createRange(); + var rangeElement = getSingleElementFromRange(range); + + // Create a new ControlRange containing all the elements in the selected ControlRange plus the element + // contained by the supplied range + var doc = getDocument(controlRange.item(0)); + var newControlRange = getBody(doc).createControlRange(); + for (var i = 0, len = controlRange.length; i < len; ++i) { + newControlRange.add(controlRange.item(i)); + } + try { + newControlRange.add(rangeElement); + } catch (ex) { + throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)"); + } + newControlRange.select(); + + // Update the wrapped selection based on what's now in the native selection + updateControlSelection(sel); + } + + var getSelectionRangeAt; + + if (isHostMethod(testSelection, "getRangeAt")) { + // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation. + // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a + // lesson to us all, especially me. + getSelectionRangeAt = function(sel, index) { + try { + return sel.getRangeAt(index); + } catch (ex) { + return null; + } + }; + } else if (selectionHasAnchorAndFocus) { + getSelectionRangeAt = function(sel) { + var doc = getDocument(sel.anchorNode); + var range = api.createRange(doc); + range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset); + + // Handle the case when the selection was selected backwards (from the end to the start in the + // document) + if (range.collapsed !== this.isCollapsed) { + range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset); + } + + return range; + }; + } + + function WrappedSelection(selection, docSelection, win) { + this.nativeSelection = selection; + this.docSelection = docSelection; + this._ranges = []; + this.win = win; + this.refresh(); + } + + WrappedSelection.prototype = api.selectionPrototype; + + function deleteProperties(sel) { + sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null; + sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0; + sel.detached = true; + } + + var cachedRangySelections = []; + + function actOnCachedSelection(win, action) { + var i = cachedRangySelections.length, cached, sel; + while (i--) { + cached = cachedRangySelections[i]; + sel = cached.selection; + if (action == "deleteAll") { + deleteProperties(sel); + } else if (cached.win == win) { + if (action == "delete") { + cachedRangySelections.splice(i, 1); + return true; + } else { + return sel; + } + } + } + if (action == "deleteAll") { + cachedRangySelections.length = 0; + } + return null; + } + + var getSelection = function(win) { + // Check if the parameter is a Rangy Selection object + if (win && win instanceof WrappedSelection) { + win.refresh(); + return win; + } + + win = getWindow(win, "getNativeSelection"); + + var sel = actOnCachedSelection(win); + var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null; + if (sel) { + sel.nativeSelection = nativeSel; + sel.docSelection = docSel; + sel.refresh(); + } else { + sel = new WrappedSelection(nativeSel, docSel, win); + cachedRangySelections.push( { win: win, selection: sel } ); + } + return sel; + }; + + api.getSelection = getSelection; + + util.createAliasForDeprecatedMethod(api, "getIframeSelection", "getSelection"); + + var selProto = WrappedSelection.prototype; + + function createControlSelection(sel, ranges) { + // Ensure that the selection becomes of type "Control" + var doc = getDocument(ranges[0].startContainer); + var controlRange = getBody(doc).createControlRange(); + for (var i = 0, el, len = ranges.length; i < len; ++i) { + el = getSingleElementFromRange(ranges[i]); + try { + controlRange.add(el); + } catch (ex) { + throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)"); + } + } + controlRange.select(); + + // Update the wrapped selection based on what's now in the native selection + updateControlSelection(sel); + } + + // Selecting a range + if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) { + selProto.removeAllRanges = function() { + this.nativeSelection.removeAllRanges(); + updateEmptySelection(this); + }; + + var addRangeBackward = function(sel, range) { + addRangeBackwardToNative(sel.nativeSelection, range); + sel.refresh(); + }; + + if (selectionHasRangeCount) { + selProto.addRange = function(range, direction) { + if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) { + addRangeToControlSelection(this, range); + } else { + if (isDirectionBackward(direction) && selectionHasExtend) { + addRangeBackward(this, range); + } else { + var previousRangeCount; + if (selectionSupportsMultipleRanges) { + previousRangeCount = this.rangeCount; + } else { + this.removeAllRanges(); + previousRangeCount = 0; + } + // Clone the native range so that changing the selected range does not affect the selection. + // This is contrary to the spec but is the only way to achieve consistency between browsers. See + // issue 80. + var clonedNativeRange = getNativeRange(range).cloneRange(); + try { + this.nativeSelection.addRange(clonedNativeRange); + } catch (ex) { + } + + // Check whether adding the range was successful + this.rangeCount = this.nativeSelection.rangeCount; + + if (this.rangeCount == previousRangeCount + 1) { + // The range was added successfully + + // Check whether the range that we added to the selection is reflected in the last range extracted from + // the selection + if (api.config.checkSelectionRanges) { + var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1); + if (nativeRange && !rangesEqual(nativeRange, range)) { + // Happens in WebKit with, for example, a selection placed at the start of a text node + range = new WrappedRange(nativeRange); + } + } + this._ranges[this.rangeCount - 1] = range; + updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection)); + this.isCollapsed = selectionIsCollapsed(this); + } else { + // The range was not added successfully. The simplest thing is to refresh + this.refresh(); + } + } + } + }; + } else { + selProto.addRange = function(range, direction) { + if (isDirectionBackward(direction) && selectionHasExtend) { + addRangeBackward(this, range); + } else { + this.nativeSelection.addRange(getNativeRange(range)); + this.refresh(); + } + }; + } + + selProto.setRanges = function(ranges) { + if (implementsControlRange && implementsDocSelection && ranges.length > 1) { + createControlSelection(this, ranges); + } else { + this.removeAllRanges(); + for (var i = 0, len = ranges.length; i < len; ++i) { + this.addRange(ranges[i]); + } + } + }; + } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") && + implementsControlRange && useDocumentSelection) { + + selProto.removeAllRanges = function() { + // Added try/catch as fix for issue #21 + try { + this.docSelection.empty(); + + // Check for empty() not working (issue #24) + if (this.docSelection.type != "None") { + // Work around failure to empty a control selection by instead selecting a TextRange and then + // calling empty() + var doc; + if (this.anchorNode) { + doc = getDocument(this.anchorNode); + } else if (this.docSelection.type == CONTROL) { + var controlRange = this.docSelection.createRange(); + if (controlRange.length) { + doc = getDocument( controlRange.item(0) ); + } + } + if (doc) { + var textRange = getBody(doc).createTextRange(); + textRange.select(); + this.docSelection.empty(); + } + } + } catch(ex) {} + updateEmptySelection(this); + }; + + selProto.addRange = function(range) { + if (this.docSelection.type == CONTROL) { + addRangeToControlSelection(this, range); + } else { + api.WrappedTextRange.rangeToTextRange(range).select(); + this._ranges[0] = range; + this.rangeCount = 1; + this.isCollapsed = this._ranges[0].collapsed; + updateAnchorAndFocusFromRange(this, range, false); + } + }; + + selProto.setRanges = function(ranges) { + this.removeAllRanges(); + var rangeCount = ranges.length; + if (rangeCount > 1) { + createControlSelection(this, ranges); + } else if (rangeCount) { + this.addRange(ranges[0]); + } + }; + } else { + module.fail("No means of selecting a Range or TextRange was found"); + return false; + } + + selProto.getRangeAt = function(index) { + if (index < 0 || index >= this.rangeCount) { + throw new DOMException("INDEX_SIZE_ERR"); + } else { + // Clone the range to preserve selection-range independence. See issue 80. + return this._ranges[index].cloneRange(); + } + }; + + var refreshSelection; + + if (useDocumentSelection) { + refreshSelection = function(sel) { + var range; + if (api.isSelectionValid(sel.win)) { + range = sel.docSelection.createRange(); + } else { + range = getBody(sel.win.document).createTextRange(); + range.collapse(true); + } + + if (sel.docSelection.type == CONTROL) { + updateControlSelection(sel); + } else if (isTextRange(range)) { + updateFromTextRange(sel, range); + } else { + updateEmptySelection(sel); + } + }; + } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) { + refreshSelection = function(sel) { + if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) { + updateControlSelection(sel); + } else { + sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount; + if (sel.rangeCount) { + for (var i = 0, len = sel.rangeCount; i < len; ++i) { + sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i)); + } + updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection)); + sel.isCollapsed = selectionIsCollapsed(sel); + } else { + updateEmptySelection(sel); + } + } + }; + } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) { + refreshSelection = function(sel) { + var range, nativeSel = sel.nativeSelection; + if (nativeSel.anchorNode) { + range = getSelectionRangeAt(nativeSel, 0); + sel._ranges = [range]; + sel.rangeCount = 1; + updateAnchorAndFocusFromNativeSelection(sel); + sel.isCollapsed = selectionIsCollapsed(sel); + } else { + updateEmptySelection(sel); + } + }; + } else { + module.fail("No means of obtaining a Range or TextRange from the user's selection was found"); + return false; + } + + selProto.refresh = function(checkForChanges) { + var oldRanges = checkForChanges ? this._ranges.slice(0) : null; + var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset; + + refreshSelection(this); + if (checkForChanges) { + // Check the range count first + var i = oldRanges.length; + if (i != this._ranges.length) { + return true; + } + + // Now check the direction. Checking the anchor position is the same is enough since we're checking all the + // ranges after this + if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) { + return true; + } + + // Finally, compare each range in turn + while (i--) { + if (!rangesEqual(oldRanges[i], this._ranges[i])) { + return true; + } + } + return false; + } + }; + + // Removal of a single range + var removeRangeManually = function(sel, range) { + var ranges = sel.getAllRanges(); + sel.removeAllRanges(); + for (var i = 0, len = ranges.length; i < len; ++i) { + if (!rangesEqual(range, ranges[i])) { + sel.addRange(ranges[i]); + } + } + if (!sel.rangeCount) { + updateEmptySelection(sel); + } + }; + + if (implementsControlRange && implementsDocSelection) { + selProto.removeRange = function(range) { + if (this.docSelection.type == CONTROL) { + var controlRange = this.docSelection.createRange(); + var rangeElement = getSingleElementFromRange(range); + + // Create a new ControlRange containing all the elements in the selected ControlRange minus the + // element contained by the supplied range + var doc = getDocument(controlRange.item(0)); + var newControlRange = getBody(doc).createControlRange(); + var el, removed = false; + for (var i = 0, len = controlRange.length; i < len; ++i) { + el = controlRange.item(i); + if (el !== rangeElement || removed) { + newControlRange.add(controlRange.item(i)); + } else { + removed = true; + } + } + newControlRange.select(); + + // Update the wrapped selection based on what's now in the native selection + updateControlSelection(this); + } else { + removeRangeManually(this, range); + } + }; + } else { + selProto.removeRange = function(range) { + removeRangeManually(this, range); + }; + } + + // Detecting if a selection is backward + var selectionIsBackward; + if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) { + selectionIsBackward = winSelectionIsBackward; + + selProto.isBackward = function() { + return selectionIsBackward(this); + }; + } else { + selectionIsBackward = selProto.isBackward = function() { + return false; + }; + } + + // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards" + selProto.isBackwards = selProto.isBackward; + + // Selection stringifier + // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation. + // The current spec does not yet define this method. + selProto.toString = function() { + var rangeTexts = []; + for (var i = 0, len = this.rangeCount; i < len; ++i) { + rangeTexts[i] = "" + this._ranges[i]; + } + return rangeTexts.join(""); + }; + + function assertNodeInSameDocument(sel, node) { + if (sel.win.document != getDocument(node)) { + throw new DOMException("WRONG_DOCUMENT_ERR"); + } + } + + // No current browser conforms fully to the spec for this method, so Rangy's own method is always used + selProto.collapse = function(node, offset) { + assertNodeInSameDocument(this, node); + var range = api.createRange(node); + range.collapseToPoint(node, offset); + this.setSingleRange(range); + this.isCollapsed = true; + }; + + selProto.collapseToStart = function() { + if (this.rangeCount) { + var range = this._ranges[0]; + this.collapse(range.startContainer, range.startOffset); + } else { + throw new DOMException("INVALID_STATE_ERR"); + } + }; + + selProto.collapseToEnd = function() { + if (this.rangeCount) { + var range = this._ranges[this.rangeCount - 1]; + this.collapse(range.endContainer, range.endOffset); + } else { + throw new DOMException("INVALID_STATE_ERR"); + } + }; + + // The spec is very specific on how selectAllChildren should be implemented and not all browsers implement it as + // specified so the native implementation is never used by Rangy. + selProto.selectAllChildren = function(node) { + assertNodeInSameDocument(this, node); + var range = api.createRange(node); + range.selectNodeContents(node); + this.setSingleRange(range); + }; + + selProto.deleteFromDocument = function() { + // Sepcial behaviour required for IE's control selections + if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) { + var controlRange = this.docSelection.createRange(); + var element; + while (controlRange.length) { + element = controlRange.item(0); + controlRange.remove(element); + dom.removeNode(element); + } + this.refresh(); + } else if (this.rangeCount) { + var ranges = this.getAllRanges(); + if (ranges.length) { + this.removeAllRanges(); + for (var i = 0, len = ranges.length; i < len; ++i) { + ranges[i].deleteContents(); + } + // The spec says nothing about what the selection should contain after calling deleteContents on each + // range. Firefox moves the selection to where the final selected range was, so we emulate that + this.addRange(ranges[len - 1]); + } + } + }; + + // The following are non-standard extensions + selProto.eachRange = function(func, returnValue) { + for (var i = 0, len = this._ranges.length; i < len; ++i) { + if ( func( this.getRangeAt(i) ) ) { + return returnValue; + } + } + }; + + selProto.getAllRanges = function() { + var ranges = []; + this.eachRange(function(range) { + ranges.push(range); + }); + return ranges; + }; + + selProto.setSingleRange = function(range, direction) { + this.removeAllRanges(); + this.addRange(range, direction); + }; + + selProto.callMethodOnEachRange = function(methodName, params) { + var results = []; + this.eachRange( function(range) { + results.push( range[methodName].apply(range, params || []) ); + } ); + return results; + }; + + function createStartOrEndSetter(isStart) { + return function(node, offset) { + var range; + if (this.rangeCount) { + range = this.getRangeAt(0); + range["set" + (isStart ? "Start" : "End")](node, offset); + } else { + range = api.createRange(this.win.document); + range.setStartAndEnd(node, offset); + } + this.setSingleRange(range, this.isBackward()); + }; + } + + selProto.setStart = createStartOrEndSetter(true); + selProto.setEnd = createStartOrEndSetter(false); + + // Add select() method to Range prototype. Any existing selection will be removed. + api.rangePrototype.select = function(direction) { + getSelection( this.getDocument() ).setSingleRange(this, direction); + }; + + selProto.changeEachRange = function(func) { + var ranges = []; + var backward = this.isBackward(); + + this.eachRange(function(range) { + func(range); + ranges.push(range); + }); + + this.removeAllRanges(); + if (backward && ranges.length == 1) { + this.addRange(ranges[0], "backward"); + } else { + this.setRanges(ranges); + } + }; + + selProto.containsNode = function(node, allowPartial) { + return this.eachRange( function(range) { + return range.containsNode(node, allowPartial); + }, true ) || false; + }; + + selProto.getBookmark = function(containerNode) { + return { + backward: this.isBackward(), + rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode]) + }; + }; + + selProto.moveToBookmark = function(bookmark) { + var selRanges = []; + for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) { + range = api.createRange(this.win); + range.moveToBookmark(rangeBookmark); + selRanges.push(range); + } + if (bookmark.backward) { + this.setSingleRange(selRanges[0], "backward"); + } else { + this.setRanges(selRanges); + } + }; + + selProto.saveRanges = function() { + return { + backward: this.isBackward(), + ranges: this.callMethodOnEachRange("cloneRange") + }; + }; + + selProto.restoreRanges = function(selRanges) { + this.removeAllRanges(); + for (var i = 0, range; range = selRanges.ranges[i]; ++i) { + this.addRange(range, (selRanges.backward && i == 0)); + } + }; + + selProto.toHtml = function() { + var rangeHtmls = []; + this.eachRange(function(range) { + rangeHtmls.push( DomRange.toHtml(range) ); + }); + return rangeHtmls.join(""); + }; + + if (features.implementsTextRange) { + selProto.getNativeTextRange = function() { + var sel, textRange; + if ( (sel = this.docSelection) ) { + var range = sel.createRange(); + if (isTextRange(range)) { + return range; + } else { + throw module.createError("getNativeTextRange: selection is a control selection"); + } + } else if (this.rangeCount > 0) { + return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) ); + } else { + throw module.createError("getNativeTextRange: selection contains no range"); + } + }; + } + + function inspect(sel) { + var rangeInspects = []; + var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset); + var focus = new DomPosition(sel.focusNode, sel.focusOffset); + var name = (typeof sel.getName == "function") ? sel.getName() : "Selection"; + + if (typeof sel.rangeCount != "undefined") { + for (var i = 0, len = sel.rangeCount; i < len; ++i) { + rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i)); + } + } + return "[" + name + "(Ranges: " + rangeInspects.join(", ") + + ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]"; + } + + selProto.getName = function() { + return "WrappedSelection"; + }; + + selProto.inspect = function() { + return inspect(this); + }; + + selProto.detach = function() { + actOnCachedSelection(this.win, "delete"); + deleteProperties(this); + }; + + WrappedSelection.detachAll = function() { + actOnCachedSelection(null, "deleteAll"); + }; + + WrappedSelection.inspect = inspect; + WrappedSelection.isDirectionBackward = isDirectionBackward; + + api.Selection = WrappedSelection; + + api.selectionPrototype = selProto; + + api.addShimListener(function(win) { + if (typeof win.getSelection == "undefined") { + win.getSelection = function() { + return getSelection(win); + }; + } + win = null; + }); + }); + + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Wait for document to load before initializing + var docReady = false; + + var loadHandler = function(e) { + if (!docReady) { + docReady = true; + if (!api.initialized && api.config.autoInitialize) { + init(); + } + } + }; + + if (isBrowser) { + // Test whether the document has already been loaded and initialize immediately if so + if (document.readyState == "complete") { + loadHandler(); + } else { + if (isHostMethod(document, "addEventListener")) { + document.addEventListener("DOMContentLoaded", loadHandler, false); + } + + // Add a fallback in case the DOMContentLoaded event isn't supported + addListener(window, "load", loadHandler); + } + } + + return api; +}, this); \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryRangyTextRange.js b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryRangyTextRange.js new file mode 100644 index 0000000000000000000000000000000000000000..4ed7e09b0a689d192acc0f0ce5efb9364b3497ed --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryRangyTextRange.js @@ -0,0 +1,1930 @@ +/** + * Text range module for Rangy. + * Text-based manipulation and searching of ranges and selections. + * + * Features + * + * - Ability to move range boundaries by character or word offsets + * - Customizable word tokenizer + * - Ignores text nodes inside <script> or <style> elements or those hidden by CSS display and visibility properties + * - Range findText method to search for text or regex within the page or within a range. Flags for whole words and case + * sensitivity + * - Selection and range save/restore as text offsets within a node + * - Methods to return visible text within a range or selection + * - innerText method for elements + * + * References + * + * https://www.w3.org/Bugs/Public/show_bug.cgi?id=13145 + * http://aryeh.name/spec/innertext/innertext.html + * http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html + * + * Part of Rangy, a cross-browser JavaScript range and selection library + * https://github.com/timdown/rangy + * + * Depends on Rangy core. + * + * Copyright 2015, Tim Down + * Licensed under the MIT license. + * Version: 1.3.0 + * Build date: 10 May 2015 + */ + +/** + * Problem: handling of trailing spaces before line breaks is handled inconsistently between browsers. + * + * First, a <br>: this is relatively simple. For the following HTML: + * + * 1 <br>2 + * + * - IE and WebKit render the space, include it in the selection (i.e. when the content is selected and pasted into a + * textarea, the space is present) and allow the caret to be placed after it. + * - Firefox does not acknowledge the space in the selection but it is possible to place the caret after it. + * - Opera does not render the space but has two separate caret positions on either side of the space (left and right + * arrow keys show this) and includes the space in the selection. + * + * The other case is the line break or breaks implied by block elements. For the following HTML: + * + * <p>1 </p><p>2<p> + * + * - WebKit does not acknowledge the space in any way + * - Firefox, IE and Opera as per <br> + * + * One more case is trailing spaces before line breaks in elements with white-space: pre-line. For the following HTML: + * + * <p style="white-space: pre-line">1 + * 2</p> + * + * - Firefox and WebKit include the space in caret positions + * - IE does not support pre-line up to and including version 9 + * - Opera ignores the space + * - Trailing space only renders if there is a non-collapsed character in the line + * + * Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be + * feature-tested + */ +(function(factory, root) { + if (typeof define == "function" && define.amd) { + // AMD. Register as an anonymous module with a dependency on Rangy. + define(["./rangy-core"], factory); + } else if (typeof module != "undefined" && typeof exports == "object") { + // Node/CommonJS style + module.exports = factory( require("rangy") ); + } else { + // No AMD or CommonJS support so we use the rangy property of root (probably the global variable) + factory(root.rangy); + } +})(function(rangy) { + rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { + var UNDEF = "undefined"; + var CHARACTER = "character", WORD = "word"; + var dom = api.dom, util = api.util; + var extend = util.extend; + var createOptions = util.createOptions; + var getBody = dom.getBody; + + + var spacesRegex = /^[ \t\f\r\n]+$/; + var spacesMinusLineBreaksRegex = /^[ \t\f\r]+$/; + var allWhiteSpaceRegex = /^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/; + var nonLineBreakWhiteSpaceRegex = /^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/; + var lineBreakRegex = /^[\n-\r\u0085\u2028\u2029]$/; + + var defaultLanguage = "en"; + + var isDirectionBackward = api.Selection.isDirectionBackward; + + // Properties representing whether trailing spaces inside blocks are completely collapsed (as they are in WebKit, + // but not other browsers). Also test whether trailing spaces before <br> elements are collapsed. + var trailingSpaceInBlockCollapses = false; + var trailingSpaceBeforeBrCollapses = false; + var trailingSpaceBeforeBlockCollapses = false; + var trailingSpaceBeforeLineBreakInPreLineCollapses = true; + + (function() { + var el = dom.createTestElement(document, "<p>1 </p><p></p>", true); + var p = el.firstChild; + var sel = api.getSelection(); + sel.collapse(p.lastChild, 2); + sel.setStart(p.firstChild, 0); + trailingSpaceInBlockCollapses = ("" + sel).length == 1; + + el.innerHTML = "1 <br />"; + sel.collapse(el, 2); + sel.setStart(el.firstChild, 0); + trailingSpaceBeforeBrCollapses = ("" + sel).length == 1; + + el.innerHTML = "1 <p>1</p>"; + sel.collapse(el, 2); + sel.setStart(el.firstChild, 0); + trailingSpaceBeforeBlockCollapses = ("" + sel).length == 1; + + dom.removeNode(el); + sel.removeAllRanges(); + })(); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // This function must create word and non-word tokens for the whole of the text supplied to it + function defaultTokenizer(chars, wordOptions) { + var word = chars.join(""), result, tokenRanges = []; + + function createTokenRange(start, end, isWord) { + tokenRanges.push( { start: start, end: end, isWord: isWord } ); + } + + // Match words and mark characters + var lastWordEnd = 0, wordStart, wordEnd; + while ( (result = wordOptions.wordRegex.exec(word)) ) { + wordStart = result.index; + wordEnd = wordStart + result[0].length; + + // Create token for non-word characters preceding this word + if (wordStart > lastWordEnd) { + createTokenRange(lastWordEnd, wordStart, false); + } + + // Get trailing space characters for word + if (wordOptions.includeTrailingSpace) { + while ( nonLineBreakWhiteSpaceRegex.test(chars[wordEnd]) ) { + ++wordEnd; + } + } + createTokenRange(wordStart, wordEnd, true); + lastWordEnd = wordEnd; + } + + // Create token for trailing non-word characters, if any exist + if (lastWordEnd < chars.length) { + createTokenRange(lastWordEnd, chars.length, false); + } + + return tokenRanges; + } + + function convertCharRangeToToken(chars, tokenRange) { + var tokenChars = chars.slice(tokenRange.start, tokenRange.end); + var token = { + isWord: tokenRange.isWord, + chars: tokenChars, + toString: function() { + return tokenChars.join(""); + } + }; + for (var i = 0, len = tokenChars.length; i < len; ++i) { + tokenChars[i].token = token; + } + return token; + } + + function tokenize(chars, wordOptions, tokenizer) { + var tokenRanges = tokenizer(chars, wordOptions); + var tokens = []; + for (var i = 0, tokenRange; tokenRange = tokenRanges[i++]; ) { + tokens.push( convertCharRangeToToken(chars, tokenRange) ); + } + return tokens; + } + + var defaultCharacterOptions = { + includeBlockContentTrailingSpace: true, + includeSpaceBeforeBr: true, + includeSpaceBeforeBlock: true, + includePreLineTrailingSpace: true, + ignoreCharacters: "" + }; + + function normalizeIgnoredCharacters(ignoredCharacters) { + // Check if character is ignored + var ignoredChars = ignoredCharacters || ""; + + // Normalize ignored characters into a string consisting of characters in ascending order of character code + var ignoredCharsArray = (typeof ignoredChars == "string") ? ignoredChars.split("") : ignoredChars; + ignoredCharsArray.sort(function(char1, char2) { + return char1.charCodeAt(0) - char2.charCodeAt(0); + }); + + /// Convert back to a string and remove duplicates + return ignoredCharsArray.join("").replace(/(.)\1+/g, "$1"); + } + + var defaultCaretCharacterOptions = { + includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses, + includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses, + includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses, + includePreLineTrailingSpace: true + }; + + var defaultWordOptions = { + "en": { + wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi, + includeTrailingSpace: false, + tokenizer: defaultTokenizer + } + }; + + var defaultFindOptions = { + caseSensitive: false, + withinRange: null, + wholeWordsOnly: false, + wrap: false, + direction: "forward", + wordOptions: null, + characterOptions: null + }; + + var defaultMoveOptions = { + wordOptions: null, + characterOptions: null + }; + + var defaultExpandOptions = { + wordOptions: null, + characterOptions: null, + trim: false, + trimStart: true, + trimEnd: true + }; + + var defaultWordIteratorOptions = { + wordOptions: null, + characterOptions: null, + direction: "forward" + }; + + function createWordOptions(options) { + var lang, defaults; + if (!options) { + return defaultWordOptions[defaultLanguage]; + } else { + lang = options.language || defaultLanguage; + defaults = {}; + extend(defaults, defaultWordOptions[lang] || defaultWordOptions[defaultLanguage]); + extend(defaults, options); + return defaults; + } + } + + function createNestedOptions(optionsParam, defaults) { + var options = createOptions(optionsParam, defaults); + if (defaults.hasOwnProperty("wordOptions")) { + options.wordOptions = createWordOptions(options.wordOptions); + } + if (defaults.hasOwnProperty("characterOptions")) { + options.characterOptions = createOptions(options.characterOptions, defaultCharacterOptions); + } + return options; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + /* DOM utility functions */ + var getComputedStyleProperty = dom.getComputedStyleProperty; + + // Create cachable versions of DOM functions + + // Test for old IE's incorrect display properties + var tableCssDisplayBlock; + (function() { + var table = document.createElement("table"); + var body = getBody(document); + body.appendChild(table); + tableCssDisplayBlock = (getComputedStyleProperty(table, "display") == "block"); + body.removeChild(table); + })(); + + var defaultDisplayValueForTag = { + table: "table", + caption: "table-caption", + colgroup: "table-column-group", + col: "table-column", + thead: "table-header-group", + tbody: "table-row-group", + tfoot: "table-footer-group", + tr: "table-row", + td: "table-cell", + th: "table-cell" + }; + + // Corrects IE's "block" value for table-related elements + function getComputedDisplay(el, win) { + var display = getComputedStyleProperty(el, "display", win); + var tagName = el.tagName.toLowerCase(); + return (display == "block" && + tableCssDisplayBlock && + defaultDisplayValueForTag.hasOwnProperty(tagName)) ? + defaultDisplayValueForTag[tagName] : display; + } + + function isHidden(node) { + var ancestors = getAncestorsAndSelf(node); + for (var i = 0, len = ancestors.length; i < len; ++i) { + if (ancestors[i].nodeType == 1 && getComputedDisplay(ancestors[i]) == "none") { + return true; + } + } + + return false; + } + + function isVisibilityHiddenTextNode(textNode) { + var el; + return textNode.nodeType == 3 && + (el = textNode.parentNode) && + getComputedStyleProperty(el, "visibility") == "hidden"; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + + // "A block node is either an Element whose "display" property does not have + // resolved value "inline" or "inline-block" or "inline-table" or "none", or a + // Document, or a DocumentFragment." + function isBlockNode(node) { + return node && + ((node.nodeType == 1 && !/^(inline(-block|-table)?|none)$/.test(getComputedDisplay(node))) || + node.nodeType == 9 || node.nodeType == 11); + } + + function getLastDescendantOrSelf(node) { + var lastChild = node.lastChild; + return lastChild ? getLastDescendantOrSelf(lastChild) : node; + } + + function containsPositions(node) { + return dom.isCharacterDataNode(node) || + !/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(node.nodeName); + } + + function getAncestors(node) { + var ancestors = []; + while (node.parentNode) { + ancestors.unshift(node.parentNode); + node = node.parentNode; + } + return ancestors; + } + + function getAncestorsAndSelf(node) { + return getAncestors(node).concat([node]); + } + + function nextNodeDescendants(node) { + while (node && !node.nextSibling) { + node = node.parentNode; + } + if (!node) { + return null; + } + return node.nextSibling; + } + + function nextNode(node, excludeChildren) { + if (!excludeChildren && node.hasChildNodes()) { + return node.firstChild; + } + return nextNodeDescendants(node); + } + + function previousNode(node) { + var previous = node.previousSibling; + if (previous) { + node = previous; + while (node.hasChildNodes()) { + node = node.lastChild; + } + return node; + } + var parent = node.parentNode; + if (parent && parent.nodeType == 1) { + return parent; + } + return null; + } + + // Adpated from Aryeh's code. + // "A whitespace node is either a Text node whose data is the empty string; or + // a Text node whose data consists only of one or more tabs (0x0009), line + // feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose + // parent is an Element whose resolved value for "white-space" is "normal" or + // "nowrap"; or a Text node whose data consists only of one or more tabs + // (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose + // parent is an Element whose resolved value for "white-space" is "pre-line"." + function isWhitespaceNode(node) { + if (!node || node.nodeType != 3) { + return false; + } + var text = node.data; + if (text === "") { + return true; + } + var parent = node.parentNode; + if (!parent || parent.nodeType != 1) { + return false; + } + var computedWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace"); + + return (/^[\t\n\r ]+$/.test(text) && /^(normal|nowrap)$/.test(computedWhiteSpace)) || + (/^[\t\r ]+$/.test(text) && computedWhiteSpace == "pre-line"); + } + + // Adpated from Aryeh's code. + // "node is a collapsed whitespace node if the following algorithm returns + // true:" + function isCollapsedWhitespaceNode(node) { + // "If node's data is the empty string, return true." + if (node.data === "") { + return true; + } + + // "If node is not a whitespace node, return false." + if (!isWhitespaceNode(node)) { + return false; + } + + // "Let ancestor be node's parent." + var ancestor = node.parentNode; + + // "If ancestor is null, return true." + if (!ancestor) { + return true; + } + + // "If the "display" property of some ancestor of node has resolved value "none", return true." + if (isHidden(node)) { + return true; + } + + return false; + } + + function isCollapsedNode(node) { + var type = node.nodeType; + return type == 7 /* PROCESSING_INSTRUCTION */ || + type == 8 /* COMMENT */ || + isHidden(node) || + /^(script|style)$/i.test(node.nodeName) || + isVisibilityHiddenTextNode(node) || + isCollapsedWhitespaceNode(node); + } + + function isIgnoredNode(node, win) { + var type = node.nodeType; + return type == 7 /* PROCESSING_INSTRUCTION */ || + type == 8 /* COMMENT */ || + (type == 1 && getComputedDisplay(node, win) == "none"); + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Possibly overengineered caching system to prevent repeated DOM calls slowing everything down + + function Cache() { + this.store = {}; + } + + Cache.prototype = { + get: function(key) { + return this.store.hasOwnProperty(key) ? this.store[key] : null; + }, + + set: function(key, value) { + return this.store[key] = value; + } + }; + + var cachedCount = 0, uncachedCount = 0; + + function createCachingGetter(methodName, func, objProperty) { + return function(args) { + var cache = this.cache; + if (cache.hasOwnProperty(methodName)) { + cachedCount++; + return cache[methodName]; + } else { + uncachedCount++; + var value = func.call(this, objProperty ? this[objProperty] : this, args); + cache[methodName] = value; + return value; + } + }; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + function NodeWrapper(node, session) { + this.node = node; + this.session = session; + this.cache = new Cache(); + this.positions = new Cache(); + } + + var nodeProto = { + getPosition: function(offset) { + var positions = this.positions; + return positions.get(offset) || positions.set(offset, new Position(this, offset)); + }, + + toString: function() { + return "[NodeWrapper(" + dom.inspectNode(this.node) + ")]"; + } + }; + + NodeWrapper.prototype = nodeProto; + + var EMPTY = "EMPTY", + NON_SPACE = "NON_SPACE", + UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE", + COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE", + TRAILING_SPACE_BEFORE_BLOCK = "TRAILING_SPACE_BEFORE_BLOCK", + TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK", + TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR", + PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK", + TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR", + INCLUDED_TRAILING_LINE_BREAK_AFTER_BR = "INCLUDED_TRAILING_LINE_BREAK_AFTER_BR"; + + extend(nodeProto, { + isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"), + getNodeIndex: createCachingGetter("nodeIndex", dom.getNodeIndex, "node"), + getLength: createCachingGetter("nodeLength", dom.getNodeLength, "node"), + containsPositions: createCachingGetter("containsPositions", containsPositions, "node"), + isWhitespace: createCachingGetter("isWhitespace", isWhitespaceNode, "node"), + isCollapsedWhitespace: createCachingGetter("isCollapsedWhitespace", isCollapsedWhitespaceNode, "node"), + getComputedDisplay: createCachingGetter("computedDisplay", getComputedDisplay, "node"), + isCollapsed: createCachingGetter("collapsed", isCollapsedNode, "node"), + isIgnored: createCachingGetter("ignored", isIgnoredNode, "node"), + next: createCachingGetter("nextPos", nextNode, "node"), + previous: createCachingGetter("previous", previousNode, "node"), + + getTextNodeInfo: createCachingGetter("textNodeInfo", function(textNode) { + var spaceRegex = null, collapseSpaces = false; + var cssWhitespace = getComputedStyleProperty(textNode.parentNode, "whiteSpace"); + var preLine = (cssWhitespace == "pre-line"); + if (preLine) { + spaceRegex = spacesMinusLineBreaksRegex; + collapseSpaces = true; + } else if (cssWhitespace == "normal" || cssWhitespace == "nowrap") { + spaceRegex = spacesRegex; + collapseSpaces = true; + } + + return { + node: textNode, + text: textNode.data, + spaceRegex: spaceRegex, + collapseSpaces: collapseSpaces, + preLine: preLine + }; + }, "node"), + + hasInnerText: createCachingGetter("hasInnerText", function(el, backward) { + var session = this.session; + var posAfterEl = session.getPosition(el.parentNode, this.getNodeIndex() + 1); + var firstPosInEl = session.getPosition(el, 0); + + var pos = backward ? posAfterEl : firstPosInEl; + var endPos = backward ? firstPosInEl : posAfterEl; + + /* + <body><p>X </p><p>Y</p></body> + + Positions: + + body:0:"" + p:0:"" + text:0:"" + text:1:"X" + text:2:TRAILING_SPACE_IN_BLOCK + text:3:COLLAPSED_SPACE + p:1:"" + body:1:"\n" + p:0:"" + text:0:"" + text:1:"Y" + + A character is a TRAILING_SPACE_IN_BLOCK iff: + + - There is no uncollapsed character after it within the visible containing block element + + A character is a TRAILING_SPACE_BEFORE_BR iff: + + - There is no uncollapsed character after it preceding a <br> element + + An element has inner text iff + + - It is not hidden + - It contains an uncollapsed character + + All trailing spaces (pre-line, before <br>, end of block) require definite non-empty characters to render. + */ + + while (pos !== endPos) { + pos.prepopulateChar(); + if (pos.isDefinitelyNonEmpty()) { + return true; + } + pos = backward ? pos.previousVisible() : pos.nextVisible(); + } + + return false; + }, "node"), + + isRenderedBlock: createCachingGetter("isRenderedBlock", function(el) { + // Ensure that a block element containing a <br> is considered to have inner text + var brs = el.getElementsByTagName("br"); + for (var i = 0, len = brs.length; i < len; ++i) { + if (!isCollapsedNode(brs[i])) { + return true; + } + } + return this.hasInnerText(); + }, "node"), + + getTrailingSpace: createCachingGetter("trailingSpace", function(el) { + if (el.tagName.toLowerCase() == "br") { + return ""; + } else { + switch (this.getComputedDisplay()) { + case "inline": + var child = el.lastChild; + while (child) { + if (!isIgnoredNode(child)) { + return (child.nodeType == 1) ? this.session.getNodeWrapper(child).getTrailingSpace() : ""; + } + child = child.previousSibling; + } + break; + case "inline-block": + case "inline-table": + case "none": + case "table-column": + case "table-column-group": + break; + case "table-cell": + return "\t"; + default: + return this.isRenderedBlock(true) ? "\n" : ""; + } + } + return ""; + }, "node"), + + getLeadingSpace: createCachingGetter("leadingSpace", function(el) { + switch (this.getComputedDisplay()) { + case "inline": + case "inline-block": + case "inline-table": + case "none": + case "table-column": + case "table-column-group": + case "table-cell": + break; + default: + return this.isRenderedBlock(false) ? "\n" : ""; + } + return ""; + }, "node") + }); + + /*----------------------------------------------------------------------------------------------------------------*/ + + function Position(nodeWrapper, offset) { + this.offset = offset; + this.nodeWrapper = nodeWrapper; + this.node = nodeWrapper.node; + this.session = nodeWrapper.session; + this.cache = new Cache(); + } + + function inspectPosition() { + return "[Position(" + dom.inspectNode(this.node) + ":" + this.offset + ")]"; + } + + var positionProto = { + character: "", + characterType: EMPTY, + isBr: false, + + /* + This method: + - Fully populates positions that have characters that can be determined independently of any other characters. + - Populates most types of space positions with a provisional character. The character is finalized later. + */ + prepopulateChar: function() { + var pos = this; + if (!pos.prepopulatedChar) { + var node = pos.node, offset = pos.offset; + var visibleChar = "", charType = EMPTY; + var finalizedChar = false; + if (offset > 0) { + if (node.nodeType == 3) { + var text = node.data; + var textChar = text.charAt(offset - 1); + + var nodeInfo = pos.nodeWrapper.getTextNodeInfo(); + var spaceRegex = nodeInfo.spaceRegex; + if (nodeInfo.collapseSpaces) { + if (spaceRegex.test(textChar)) { + // "If the character at position is from set, append a single space (U+0020) to newdata and advance + // position until the character at position is not from set." + + // We also need to check for the case where we're in a pre-line and we have a space preceding a + // line break, because such spaces are collapsed in some browsers + if (offset > 1 && spaceRegex.test(text.charAt(offset - 2))) { + } else if (nodeInfo.preLine && text.charAt(offset) === "\n") { + visibleChar = " "; + charType = PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK; + } else { + visibleChar = " "; + //pos.checkForFollowingLineBreak = true; + charType = COLLAPSIBLE_SPACE; + } + } else { + visibleChar = textChar; + charType = NON_SPACE; + finalizedChar = true; + } + } else { + visibleChar = textChar; + charType = UNCOLLAPSIBLE_SPACE; + finalizedChar = true; + } + } else { + var nodePassed = node.childNodes[offset - 1]; + if (nodePassed && nodePassed.nodeType == 1 && !isCollapsedNode(nodePassed)) { + if (nodePassed.tagName.toLowerCase() == "br") { + visibleChar = "\n"; + pos.isBr = true; + charType = COLLAPSIBLE_SPACE; + finalizedChar = false; + } else { + pos.checkForTrailingSpace = true; + } + } + + // Check the leading space of the next node for the case when a block element follows an inline + // element or text node. In that case, there is an implied line break between the two nodes. + if (!visibleChar) { + var nextNode = node.childNodes[offset]; + if (nextNode && nextNode.nodeType == 1 && !isCollapsedNode(nextNode)) { + pos.checkForLeadingSpace = true; + } + } + } + } + + pos.prepopulatedChar = true; + pos.character = visibleChar; + pos.characterType = charType; + pos.isCharInvariant = finalizedChar; + } + }, + + isDefinitelyNonEmpty: function() { + var charType = this.characterType; + return charType == NON_SPACE || charType == UNCOLLAPSIBLE_SPACE; + }, + + // Resolve leading and trailing spaces, which may involve prepopulating other positions + resolveLeadingAndTrailingSpaces: function() { + if (!this.prepopulatedChar) { + this.prepopulateChar(); + } + if (this.checkForTrailingSpace) { + var trailingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset - 1]).getTrailingSpace(); + if (trailingSpace) { + this.isTrailingSpace = true; + this.character = trailingSpace; + this.characterType = COLLAPSIBLE_SPACE; + } + this.checkForTrailingSpace = false; + } + if (this.checkForLeadingSpace) { + var leadingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace(); + if (leadingSpace) { + this.isLeadingSpace = true; + this.character = leadingSpace; + this.characterType = COLLAPSIBLE_SPACE; + } + this.checkForLeadingSpace = false; + } + }, + + getPrecedingUncollapsedPosition: function(characterOptions) { + var pos = this, character; + while ( (pos = pos.previousVisible()) ) { + character = pos.getCharacter(characterOptions); + if (character !== "") { + return pos; + } + } + + return null; + }, + + getCharacter: function(characterOptions) { + this.resolveLeadingAndTrailingSpaces(); + + var thisChar = this.character, returnChar; + + // Check if character is ignored + var ignoredChars = normalizeIgnoredCharacters(characterOptions.ignoreCharacters); + var isIgnoredCharacter = (thisChar !== "" && ignoredChars.indexOf(thisChar) > -1); + + // Check if this position's character is invariant (i.e. not dependent on character options) and return it + // if so + if (this.isCharInvariant) { + returnChar = isIgnoredCharacter ? "" : thisChar; + return returnChar; + } + + var cacheKey = ["character", characterOptions.includeSpaceBeforeBr, characterOptions.includeBlockContentTrailingSpace, characterOptions.includePreLineTrailingSpace, ignoredChars].join("_"); + var cachedChar = this.cache.get(cacheKey); + if (cachedChar !== null) { + return cachedChar; + } + + // We need to actually get the character now + var character = ""; + var collapsible = (this.characterType == COLLAPSIBLE_SPACE); + + var nextPos, previousPos; + var gotPreviousPos = false; + var pos = this; + + function getPreviousPos() { + if (!gotPreviousPos) { + previousPos = pos.getPrecedingUncollapsedPosition(characterOptions); + gotPreviousPos = true; + } + return previousPos; + } + + // Disallow a collapsible space that is followed by a line break or is the last character + if (collapsible) { + // Allow a trailing space that we've previously determined should be included + if (this.type == INCLUDED_TRAILING_LINE_BREAK_AFTER_BR) { + character = "\n"; + } + // Disallow a collapsible space that follows a trailing space or line break, or is the first character, + // or follows a collapsible included space + else if (thisChar == " " && + (!getPreviousPos() || previousPos.isTrailingSpace || previousPos.character == "\n" || (previousPos.character == " " && previousPos.characterType == COLLAPSIBLE_SPACE))) { + } + // Allow a leading line break unless it follows a line break + else if (thisChar == "\n" && this.isLeadingSpace) { + if (getPreviousPos() && previousPos.character != "\n") { + character = "\n"; + } else { + } + } else { + nextPos = this.nextUncollapsed(); + if (nextPos) { + if (nextPos.isBr) { + this.type = TRAILING_SPACE_BEFORE_BR; + } else if (nextPos.isTrailingSpace && nextPos.character == "\n") { + this.type = TRAILING_SPACE_IN_BLOCK; + } else if (nextPos.isLeadingSpace && nextPos.character == "\n") { + this.type = TRAILING_SPACE_BEFORE_BLOCK; + } + + if (nextPos.character == "\n") { + if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) { + } else if (this.type == TRAILING_SPACE_BEFORE_BLOCK && !characterOptions.includeSpaceBeforeBlock) { + } else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) { + } else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) { + } else if (thisChar == "\n") { + if (nextPos.isTrailingSpace) { + if (this.isTrailingSpace) { + } else if (this.isBr) { + nextPos.type = TRAILING_LINE_BREAK_AFTER_BR; + + if (getPreviousPos() && previousPos.isLeadingSpace && !previousPos.isTrailingSpace && previousPos.character == "\n") { + nextPos.character = ""; + } else { + nextPos.type = INCLUDED_TRAILING_LINE_BREAK_AFTER_BR; + } + } + } else { + character = "\n"; + } + } else if (thisChar == " ") { + character = " "; + } else { + } + } else { + character = thisChar; + } + } else { + } + } + } + + if (ignoredChars.indexOf(character) > -1) { + character = ""; + } + + + this.cache.set(cacheKey, character); + + return character; + }, + + equals: function(pos) { + return !!pos && this.node === pos.node && this.offset === pos.offset; + }, + + inspect: inspectPosition, + + toString: function() { + return this.character; + } + }; + + Position.prototype = positionProto; + + extend(positionProto, { + next: createCachingGetter("nextPos", function(pos) { + var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session; + if (!node) { + return null; + } + var nextNode, nextOffset, child; + if (offset == nodeWrapper.getLength()) { + // Move onto the next node + nextNode = node.parentNode; + nextOffset = nextNode ? nodeWrapper.getNodeIndex() + 1 : 0; + } else { + if (nodeWrapper.isCharacterDataNode()) { + nextNode = node; + nextOffset = offset + 1; + } else { + child = node.childNodes[offset]; + // Go into the children next, if children there are + if (session.getNodeWrapper(child).containsPositions()) { + nextNode = child; + nextOffset = 0; + } else { + nextNode = node; + nextOffset = offset + 1; + } + } + } + + return nextNode ? session.getPosition(nextNode, nextOffset) : null; + }), + + previous: createCachingGetter("previous", function(pos) { + var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session; + var previousNode, previousOffset, child; + if (offset == 0) { + previousNode = node.parentNode; + previousOffset = previousNode ? nodeWrapper.getNodeIndex() : 0; + } else { + if (nodeWrapper.isCharacterDataNode()) { + previousNode = node; + previousOffset = offset - 1; + } else { + child = node.childNodes[offset - 1]; + // Go into the children next, if children there are + if (session.getNodeWrapper(child).containsPositions()) { + previousNode = child; + previousOffset = dom.getNodeLength(child); + } else { + previousNode = node; + previousOffset = offset - 1; + } + } + } + return previousNode ? session.getPosition(previousNode, previousOffset) : null; + }), + + /* + Next and previous position moving functions that filter out + + - Hidden (CSS visibility/display) elements + - Script and style elements + */ + nextVisible: createCachingGetter("nextVisible", function(pos) { + var next = pos.next(); + if (!next) { + return null; + } + var nodeWrapper = next.nodeWrapper, node = next.node; + var newPos = next; + if (nodeWrapper.isCollapsed()) { + // We're skipping this node and all its descendants + newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex() + 1); + } + return newPos; + }), + + nextUncollapsed: createCachingGetter("nextUncollapsed", function(pos) { + var nextPos = pos; + while ( (nextPos = nextPos.nextVisible()) ) { + nextPos.resolveLeadingAndTrailingSpaces(); + if (nextPos.character !== "") { + return nextPos; + } + } + return null; + }), + + previousVisible: createCachingGetter("previousVisible", function(pos) { + var previous = pos.previous(); + if (!previous) { + return null; + } + var nodeWrapper = previous.nodeWrapper, node = previous.node; + var newPos = previous; + if (nodeWrapper.isCollapsed()) { + // We're skipping this node and all its descendants + newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex()); + } + return newPos; + }) + }); + + /*----------------------------------------------------------------------------------------------------------------*/ + + var currentSession = null; + + var Session = (function() { + function createWrapperCache(nodeProperty) { + var cache = new Cache(); + + return { + get: function(node) { + var wrappersByProperty = cache.get(node[nodeProperty]); + if (wrappersByProperty) { + for (var i = 0, wrapper; wrapper = wrappersByProperty[i++]; ) { + if (wrapper.node === node) { + return wrapper; + } + } + } + return null; + }, + + set: function(nodeWrapper) { + var property = nodeWrapper.node[nodeProperty]; + var wrappersByProperty = cache.get(property) || cache.set(property, []); + wrappersByProperty.push(nodeWrapper); + } + }; + } + + var uniqueIDSupported = util.isHostProperty(document.documentElement, "uniqueID"); + + function Session() { + this.initCaches(); + } + + Session.prototype = { + initCaches: function() { + this.elementCache = uniqueIDSupported ? (function() { + var elementsCache = new Cache(); + + return { + get: function(el) { + return elementsCache.get(el.uniqueID); + }, + + set: function(elWrapper) { + elementsCache.set(elWrapper.node.uniqueID, elWrapper); + } + }; + })() : createWrapperCache("tagName"); + + // Store text nodes keyed by data, although we may need to truncate this + this.textNodeCache = createWrapperCache("data"); + this.otherNodeCache = createWrapperCache("nodeName"); + }, + + getNodeWrapper: function(node) { + var wrapperCache; + switch (node.nodeType) { + case 1: + wrapperCache = this.elementCache; + break; + case 3: + wrapperCache = this.textNodeCache; + break; + default: + wrapperCache = this.otherNodeCache; + break; + } + + var wrapper = wrapperCache.get(node); + if (!wrapper) { + wrapper = new NodeWrapper(node, this); + wrapperCache.set(wrapper); + } + return wrapper; + }, + + getPosition: function(node, offset) { + return this.getNodeWrapper(node).getPosition(offset); + }, + + getRangeBoundaryPosition: function(range, isStart) { + var prefix = isStart ? "start" : "end"; + return this.getPosition(range[prefix + "Container"], range[prefix + "Offset"]); + }, + + detach: function() { + this.elementCache = this.textNodeCache = this.otherNodeCache = null; + } + }; + + return Session; + })(); + + /*----------------------------------------------------------------------------------------------------------------*/ + + function startSession() { + endSession(); + return (currentSession = new Session()); + } + + function getSession() { + return currentSession || startSession(); + } + + function endSession() { + if (currentSession) { + currentSession.detach(); + } + currentSession = null; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Extensions to the rangy.dom utility object + + extend(dom, { + nextNode: nextNode, + previousNode: previousNode + }); + + /*----------------------------------------------------------------------------------------------------------------*/ + + function createCharacterIterator(startPos, backward, endPos, characterOptions) { + + // Adjust the end position to ensure that it is actually reached + if (endPos) { + if (backward) { + if (isCollapsedNode(endPos.node)) { + endPos = startPos.previousVisible(); + } + } else { + if (isCollapsedNode(endPos.node)) { + endPos = endPos.nextVisible(); + } + } + } + + var pos = startPos, finished = false; + + function next() { + var charPos = null; + if (backward) { + charPos = pos; + if (!finished) { + pos = pos.previousVisible(); + finished = !pos || (endPos && pos.equals(endPos)); + } + } else { + if (!finished) { + charPos = pos = pos.nextVisible(); + finished = !pos || (endPos && pos.equals(endPos)); + } + } + if (finished) { + pos = null; + } + return charPos; + } + + var previousTextPos, returnPreviousTextPos = false; + + return { + next: function() { + if (returnPreviousTextPos) { + returnPreviousTextPos = false; + return previousTextPos; + } else { + var pos, character; + while ( (pos = next()) ) { + character = pos.getCharacter(characterOptions); + if (character) { + previousTextPos = pos; + return pos; + } + } + return null; + } + }, + + rewind: function() { + if (previousTextPos) { + returnPreviousTextPos = true; + } else { + throw module.createError("createCharacterIterator: cannot rewind. Only one position can be rewound."); + } + }, + + dispose: function() { + startPos = endPos = null; + } + }; + } + + var arrayIndexOf = Array.prototype.indexOf ? + function(arr, val) { + return arr.indexOf(val); + } : + function(arr, val) { + for (var i = 0, len = arr.length; i < len; ++i) { + if (arr[i] === val) { + return i; + } + } + return -1; + }; + + // Provides a pair of iterators over text positions, tokenized. Transparently requests more text when next() + // is called and there is no more tokenized text + function createTokenizedTextProvider(pos, characterOptions, wordOptions) { + var forwardIterator = createCharacterIterator(pos, false, null, characterOptions); + var backwardIterator = createCharacterIterator(pos, true, null, characterOptions); + var tokenizer = wordOptions.tokenizer; + + // Consumes a word and the whitespace beyond it + function consumeWord(forward) { + var pos, textChar; + var newChars = [], it = forward ? forwardIterator : backwardIterator; + + var passedWordBoundary = false, insideWord = false; + + while ( (pos = it.next()) ) { + textChar = pos.character; + + + if (allWhiteSpaceRegex.test(textChar)) { + if (insideWord) { + insideWord = false; + passedWordBoundary = true; + } + } else { + if (passedWordBoundary) { + it.rewind(); + break; + } else { + insideWord = true; + } + } + newChars.push(pos); + } + + + return newChars; + } + + // Get initial word surrounding initial position and tokenize it + var forwardChars = consumeWord(true); + var backwardChars = consumeWord(false).reverse(); + var tokens = tokenize(backwardChars.concat(forwardChars), wordOptions, tokenizer); + + // Create initial token buffers + var forwardTokensBuffer = forwardChars.length ? + tokens.slice(arrayIndexOf(tokens, forwardChars[0].token)) : []; + + var backwardTokensBuffer = backwardChars.length ? + tokens.slice(0, arrayIndexOf(tokens, backwardChars.pop().token) + 1) : []; + + function inspectBuffer(buffer) { + var textPositions = ["[" + buffer.length + "]"]; + for (var i = 0; i < buffer.length; ++i) { + textPositions.push("(word: " + buffer[i] + ", is word: " + buffer[i].isWord + ")"); + } + return textPositions; + } + + + return { + nextEndToken: function() { + var lastToken, forwardChars; + + // If we're down to the last token, consume character chunks until we have a word or run out of + // characters to consume + while ( forwardTokensBuffer.length == 1 && + !(lastToken = forwardTokensBuffer[0]).isWord && + (forwardChars = consumeWord(true)).length > 0) { + + // Merge trailing non-word into next word and tokenize + forwardTokensBuffer = tokenize(lastToken.chars.concat(forwardChars), wordOptions, tokenizer); + } + + return forwardTokensBuffer.shift(); + }, + + previousStartToken: function() { + var lastToken, backwardChars; + + // If we're down to the last token, consume character chunks until we have a word or run out of + // characters to consume + while ( backwardTokensBuffer.length == 1 && + !(lastToken = backwardTokensBuffer[0]).isWord && + (backwardChars = consumeWord(false)).length > 0) { + + // Merge leading non-word into next word and tokenize + backwardTokensBuffer = tokenize(backwardChars.reverse().concat(lastToken.chars), wordOptions, tokenizer); + } + + return backwardTokensBuffer.pop(); + }, + + dispose: function() { + forwardIterator.dispose(); + backwardIterator.dispose(); + forwardTokensBuffer = backwardTokensBuffer = null; + } + }; + } + + function movePositionBy(pos, unit, count, characterOptions, wordOptions) { + var unitsMoved = 0, currentPos, newPos = pos, charIterator, nextPos, absCount = Math.abs(count), token; + if (count !== 0) { + var backward = (count < 0); + + switch (unit) { + case CHARACTER: + charIterator = createCharacterIterator(pos, backward, null, characterOptions); + while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) { + ++unitsMoved; + newPos = currentPos; + } + nextPos = currentPos; + charIterator.dispose(); + break; + case WORD: + var tokenizedTextProvider = createTokenizedTextProvider(pos, characterOptions, wordOptions); + var next = backward ? tokenizedTextProvider.previousStartToken : tokenizedTextProvider.nextEndToken; + + while ( (token = next()) && unitsMoved < absCount ) { + if (token.isWord) { + ++unitsMoved; + newPos = backward ? token.chars[0] : token.chars[token.chars.length - 1]; + } + } + break; + default: + throw new Error("movePositionBy: unit '" + unit + "' not implemented"); + } + + // Perform any necessary position tweaks + if (backward) { + newPos = newPos.previousVisible(); + unitsMoved = -unitsMoved; + } else if (newPos && newPos.isLeadingSpace && !newPos.isTrailingSpace) { + // Tweak the position for the case of a leading space. The problem is that an uncollapsed leading space + // before a block element (for example, the line break between "1" and "2" in the following HTML: + // "1<p>2</p>") is considered to be attached to the position immediately before the block element, which + // corresponds with a different selection position in most browsers from the one we want (i.e. at the + // start of the contents of the block element). We get round this by advancing the position returned to + // the last possible equivalent visible position. + if (unit == WORD) { + charIterator = createCharacterIterator(pos, false, null, characterOptions); + nextPos = charIterator.next(); + charIterator.dispose(); + } + if (nextPos) { + newPos = nextPos.previousVisible(); + } + } + } + + + return { + position: newPos, + unitsMoved: unitsMoved + }; + } + + function createRangeCharacterIterator(session, range, characterOptions, backward) { + var rangeStart = session.getRangeBoundaryPosition(range, true); + var rangeEnd = session.getRangeBoundaryPosition(range, false); + var itStart = backward ? rangeEnd : rangeStart; + var itEnd = backward ? rangeStart : rangeEnd; + + return createCharacterIterator(itStart, !!backward, itEnd, characterOptions); + } + + function getRangeCharacters(session, range, characterOptions) { + + var chars = [], it = createRangeCharacterIterator(session, range, characterOptions), pos; + while ( (pos = it.next()) ) { + chars.push(pos); + } + + it.dispose(); + return chars; + } + + function isWholeWord(startPos, endPos, wordOptions) { + var range = api.createRange(startPos.node); + range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset); + return !range.expand("word", { wordOptions: wordOptions }); + } + + function findTextFromPosition(initialPos, searchTerm, isRegex, searchScopeRange, findOptions) { + var backward = isDirectionBackward(findOptions.direction); + var it = createCharacterIterator( + initialPos, + backward, + initialPos.session.getRangeBoundaryPosition(searchScopeRange, backward), + findOptions.characterOptions + ); + var text = "", chars = [], pos, currentChar, matchStartIndex, matchEndIndex; + var result, insideRegexMatch; + var returnValue = null; + + function handleMatch(startIndex, endIndex) { + var startPos = chars[startIndex].previousVisible(); + var endPos = chars[endIndex - 1]; + var valid = (!findOptions.wholeWordsOnly || isWholeWord(startPos, endPos, findOptions.wordOptions)); + + return { + startPos: startPos, + endPos: endPos, + valid: valid + }; + } + + while ( (pos = it.next()) ) { + currentChar = pos.character; + if (!isRegex && !findOptions.caseSensitive) { + currentChar = currentChar.toLowerCase(); + } + + if (backward) { + chars.unshift(pos); + text = currentChar + text; + } else { + chars.push(pos); + text += currentChar; + } + + if (isRegex) { + result = searchTerm.exec(text); + if (result) { + matchStartIndex = result.index; + matchEndIndex = matchStartIndex + result[0].length; + if (insideRegexMatch) { + // Check whether the match is now over + if ((!backward && matchEndIndex < text.length) || (backward && matchStartIndex > 0)) { + returnValue = handleMatch(matchStartIndex, matchEndIndex); + break; + } + } else { + insideRegexMatch = true; + } + } + } else if ( (matchStartIndex = text.indexOf(searchTerm)) != -1 ) { + returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length); + break; + } + } + + // Check whether regex match extends to the end of the range + if (insideRegexMatch) { + returnValue = handleMatch(matchStartIndex, matchEndIndex); + } + it.dispose(); + + return returnValue; + } + + function createEntryPointFunction(func) { + return function() { + var sessionRunning = !!currentSession; + var session = getSession(); + var args = [session].concat( util.toArray(arguments) ); + var returnValue = func.apply(this, args); + if (!sessionRunning) { + endSession(); + } + return returnValue; + }; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Extensions to the Rangy Range object + + function createRangeBoundaryMover(isStart, collapse) { + /* + Unit can be "character" or "word" + Options: + + - includeTrailingSpace + - wordRegex + - tokenizer + - collapseSpaceBeforeLineBreak + */ + return createEntryPointFunction( + function(session, unit, count, moveOptions) { + if (typeof count == UNDEF) { + count = unit; + unit = CHARACTER; + } + moveOptions = createNestedOptions(moveOptions, defaultMoveOptions); + + var boundaryIsStart = isStart; + if (collapse) { + boundaryIsStart = (count >= 0); + this.collapse(!boundaryIsStart); + } + var moveResult = movePositionBy(session.getRangeBoundaryPosition(this, boundaryIsStart), unit, count, moveOptions.characterOptions, moveOptions.wordOptions); + var newPos = moveResult.position; + this[boundaryIsStart ? "setStart" : "setEnd"](newPos.node, newPos.offset); + return moveResult.unitsMoved; + } + ); + } + + function createRangeTrimmer(isStart) { + return createEntryPointFunction( + function(session, characterOptions) { + characterOptions = createOptions(characterOptions, defaultCharacterOptions); + var pos; + var it = createRangeCharacterIterator(session, this, characterOptions, !isStart); + var trimCharCount = 0; + while ( (pos = it.next()) && allWhiteSpaceRegex.test(pos.character) ) { + ++trimCharCount; + } + it.dispose(); + var trimmed = (trimCharCount > 0); + if (trimmed) { + this[isStart ? "moveStart" : "moveEnd"]( + "character", + isStart ? trimCharCount : -trimCharCount, + { characterOptions: characterOptions } + ); + } + return trimmed; + } + ); + } + + extend(api.rangePrototype, { + moveStart: createRangeBoundaryMover(true, false), + + moveEnd: createRangeBoundaryMover(false, false), + + move: createRangeBoundaryMover(true, true), + + trimStart: createRangeTrimmer(true), + + trimEnd: createRangeTrimmer(false), + + trim: createEntryPointFunction( + function(session, characterOptions) { + var startTrimmed = this.trimStart(characterOptions), endTrimmed = this.trimEnd(characterOptions); + return startTrimmed || endTrimmed; + } + ), + + expand: createEntryPointFunction( + function(session, unit, expandOptions) { + var moved = false; + expandOptions = createNestedOptions(expandOptions, defaultExpandOptions); + var characterOptions = expandOptions.characterOptions; + if (!unit) { + unit = CHARACTER; + } + if (unit == WORD) { + var wordOptions = expandOptions.wordOptions; + var startPos = session.getRangeBoundaryPosition(this, true); + var endPos = session.getRangeBoundaryPosition(this, false); + + var startTokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions); + var startToken = startTokenizedTextProvider.nextEndToken(); + var newStartPos = startToken.chars[0].previousVisible(); + var endToken, newEndPos; + + if (this.collapsed) { + endToken = startToken; + } else { + var endTokenizedTextProvider = createTokenizedTextProvider(endPos, characterOptions, wordOptions); + endToken = endTokenizedTextProvider.previousStartToken(); + } + newEndPos = endToken.chars[endToken.chars.length - 1]; + + if (!newStartPos.equals(startPos)) { + this.setStart(newStartPos.node, newStartPos.offset); + moved = true; + } + if (newEndPos && !newEndPos.equals(endPos)) { + this.setEnd(newEndPos.node, newEndPos.offset); + moved = true; + } + + if (expandOptions.trim) { + if (expandOptions.trimStart) { + moved = this.trimStart(characterOptions) || moved; + } + if (expandOptions.trimEnd) { + moved = this.trimEnd(characterOptions) || moved; + } + } + + return moved; + } else { + return this.moveEnd(CHARACTER, 1, expandOptions); + } + } + ), + + text: createEntryPointFunction( + function(session, characterOptions) { + return this.collapsed ? + "" : getRangeCharacters(session, this, createOptions(characterOptions, defaultCharacterOptions)).join(""); + } + ), + + selectCharacters: createEntryPointFunction( + function(session, containerNode, startIndex, endIndex, characterOptions) { + var moveOptions = { characterOptions: characterOptions }; + if (!containerNode) { + containerNode = getBody( this.getDocument() ); + } + this.selectNodeContents(containerNode); + this.collapse(true); + this.moveStart("character", startIndex, moveOptions); + this.collapse(true); + this.moveEnd("character", endIndex - startIndex, moveOptions); + } + ), + + // Character indexes are relative to the start of node + toCharacterRange: createEntryPointFunction( + function(session, containerNode, characterOptions) { + if (!containerNode) { + containerNode = getBody( this.getDocument() ); + } + var parent = containerNode.parentNode, nodeIndex = dom.getNodeIndex(containerNode); + var rangeStartsBeforeNode = (dom.comparePoints(this.startContainer, this.endContainer, parent, nodeIndex) == -1); + var rangeBetween = this.cloneRange(); + var startIndex, endIndex; + if (rangeStartsBeforeNode) { + rangeBetween.setStartAndEnd(this.startContainer, this.startOffset, parent, nodeIndex); + startIndex = -rangeBetween.text(characterOptions).length; + } else { + rangeBetween.setStartAndEnd(parent, nodeIndex, this.startContainer, this.startOffset); + startIndex = rangeBetween.text(characterOptions).length; + } + endIndex = startIndex + this.text(characterOptions).length; + + return { + start: startIndex, + end: endIndex + }; + } + ), + + findText: createEntryPointFunction( + function(session, searchTermParam, findOptions) { + // Set up options + findOptions = createNestedOptions(findOptions, defaultFindOptions); + + // Create word options if we're matching whole words only + if (findOptions.wholeWordsOnly) { + // We don't ever want trailing spaces for search results + findOptions.wordOptions.includeTrailingSpace = false; + } + + var backward = isDirectionBackward(findOptions.direction); + + // Create a range representing the search scope if none was provided + var searchScopeRange = findOptions.withinRange; + if (!searchScopeRange) { + searchScopeRange = api.createRange(); + searchScopeRange.selectNodeContents(this.getDocument()); + } + + // Examine and prepare the search term + var searchTerm = searchTermParam, isRegex = false; + if (typeof searchTerm == "string") { + if (!findOptions.caseSensitive) { + searchTerm = searchTerm.toLowerCase(); + } + } else { + isRegex = true; + } + + var initialPos = session.getRangeBoundaryPosition(this, !backward); + + // Adjust initial position if it lies outside the search scope + var comparison = searchScopeRange.comparePoint(initialPos.node, initialPos.offset); + + if (comparison === -1) { + initialPos = session.getRangeBoundaryPosition(searchScopeRange, true); + } else if (comparison === 1) { + initialPos = session.getRangeBoundaryPosition(searchScopeRange, false); + } + + var pos = initialPos; + var wrappedAround = false; + + // Try to find a match and ignore invalid ones + var findResult; + while (true) { + findResult = findTextFromPosition(pos, searchTerm, isRegex, searchScopeRange, findOptions); + + if (findResult) { + if (findResult.valid) { + this.setStartAndEnd(findResult.startPos.node, findResult.startPos.offset, findResult.endPos.node, findResult.endPos.offset); + return true; + } else { + // We've found a match that is not a whole word, so we carry on searching from the point immediately + // after the match + pos = backward ? findResult.startPos : findResult.endPos; + } + } else if (findOptions.wrap && !wrappedAround) { + // No result found but we're wrapping around and limiting the scope to the unsearched part of the range + searchScopeRange = searchScopeRange.cloneRange(); + pos = session.getRangeBoundaryPosition(searchScopeRange, !backward); + searchScopeRange.setBoundary(initialPos.node, initialPos.offset, backward); + wrappedAround = true; + } else { + // Nothing found and we can't wrap around, so we're done + return false; + } + } + } + ), + + pasteHtml: function(html) { + this.deleteContents(); + if (html) { + var frag = this.createContextualFragment(html); + var lastChild = frag.lastChild; + this.insertNode(frag); + this.collapseAfter(lastChild); + } + } + }); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Extensions to the Rangy Selection object + + function createSelectionTrimmer(methodName) { + return createEntryPointFunction( + function(session, characterOptions) { + var trimmed = false; + this.changeEachRange(function(range) { + trimmed = range[methodName](characterOptions) || trimmed; + }); + return trimmed; + } + ); + } + + extend(api.selectionPrototype, { + expand: createEntryPointFunction( + function(session, unit, expandOptions) { + this.changeEachRange(function(range) { + range.expand(unit, expandOptions); + }); + } + ), + + move: createEntryPointFunction( + function(session, unit, count, options) { + var unitsMoved = 0; + if (this.focusNode) { + this.collapse(this.focusNode, this.focusOffset); + var range = this.getRangeAt(0); + if (!options) { + options = {}; + } + options.characterOptions = createOptions(options.characterOptions, defaultCaretCharacterOptions); + unitsMoved = range.move(unit, count, options); + this.setSingleRange(range); + } + return unitsMoved; + } + ), + + trimStart: createSelectionTrimmer("trimStart"), + trimEnd: createSelectionTrimmer("trimEnd"), + trim: createSelectionTrimmer("trim"), + + selectCharacters: createEntryPointFunction( + function(session, containerNode, startIndex, endIndex, direction, characterOptions) { + var range = api.createRange(containerNode); + range.selectCharacters(containerNode, startIndex, endIndex, characterOptions); + this.setSingleRange(range, direction); + } + ), + + saveCharacterRanges: createEntryPointFunction( + function(session, containerNode, characterOptions) { + var ranges = this.getAllRanges(), rangeCount = ranges.length; + var rangeInfos = []; + + var backward = rangeCount == 1 && this.isBackward(); + + for (var i = 0, len = ranges.length; i < len; ++i) { + rangeInfos[i] = { + characterRange: ranges[i].toCharacterRange(containerNode, characterOptions), + backward: backward, + characterOptions: characterOptions + }; + } + + return rangeInfos; + } + ), + + restoreCharacterRanges: createEntryPointFunction( + function(session, containerNode, saved) { + this.removeAllRanges(); + for (var i = 0, len = saved.length, range, rangeInfo, characterRange; i < len; ++i) { + rangeInfo = saved[i]; + characterRange = rangeInfo.characterRange; + range = api.createRange(containerNode); + range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions); + this.addRange(range, rangeInfo.backward); + } + } + ), + + text: createEntryPointFunction( + function(session, characterOptions) { + var rangeTexts = []; + for (var i = 0, len = this.rangeCount; i < len; ++i) { + rangeTexts[i] = this.getRangeAt(i).text(characterOptions); + } + return rangeTexts.join(""); + } + ) + }); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Extensions to the core rangy object + + api.innerText = function(el, characterOptions) { + var range = api.createRange(el); + range.selectNodeContents(el); + var text = range.text(characterOptions); + return text; + }; + + api.createWordIterator = function(startNode, startOffset, iteratorOptions) { + var session = getSession(); + iteratorOptions = createNestedOptions(iteratorOptions, defaultWordIteratorOptions); + var startPos = session.getPosition(startNode, startOffset); + var tokenizedTextProvider = createTokenizedTextProvider(startPos, iteratorOptions.characterOptions, iteratorOptions.wordOptions); + var backward = isDirectionBackward(iteratorOptions.direction); + + return { + next: function() { + return backward ? tokenizedTextProvider.previousStartToken() : tokenizedTextProvider.nextEndToken(); + }, + + dispose: function() { + tokenizedTextProvider.dispose(); + this.next = function() {}; + } + }; + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + api.noMutation = function(func) { + var session = getSession(); + func(session); + endSession(); + }; + + api.noMutation.createEntryPointFunction = createEntryPointFunction; + + api.textRange = { + isBlockNode: isBlockNode, + isCollapsedWhitespaceNode: isCollapsedWhitespaceNode, + + createPosition: createEntryPointFunction( + function(session, node, offset) { + return session.getPosition(node, offset); + } + ) + }; + }); + + return rangy; +}, this); \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryScrollTo.js b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryScrollTo.js new file mode 100644 index 0000000000000000000000000000000000000000..ead8108b53c912b8c89a209443c6341c237451bc --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryScrollTo.js @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2007-2015 Ariel Flesler - aflesler<a>gmail<d>com | http://flesler.blogspot.com + * Licensed under MIT + * @author Ariel Flesler + * @version 2.1.2 + */ +;(function(f){"use strict";"function"===typeof define&&define.amd?define(["jquery"],f):"undefined"!==typeof module&&module.exports?module.exports=f(require("jquery")):f(jQuery)})(function($){"use strict";function n(a){return!a.nodeName||-1!==$.inArray(a.nodeName.toLowerCase(),["iframe","#document","html","body"])}function h(a){return $.isFunction(a)||$.isPlainObject(a)?a:{top:a,left:a}}var p=$.scrollTo=function(a,d,b){return $(window).scrollTo(a,d,b)};p.defaults={axis:"xy",duration:0,limit:!0};$.fn.scrollTo=function(a,d,b){"object"=== typeof d&&(b=d,d=0);"function"===typeof b&&(b={onAfter:b});"max"===a&&(a=9E9);b=$.extend({},p.defaults,b);d=d||b.duration;var u=b.queue&&1<b.axis.length;u&&(d/=2);b.offset=h(b.offset);b.over=h(b.over);return this.each(function(){function k(a){var k=$.extend({},b,{queue:!0,duration:d,complete:a&&function(){a.call(q,e,b)}});r.animate(f,k)}if(null!==a){var l=n(this),q=l?this.contentWindow||window:this,r=$(q),e=a,f={},t;switch(typeof e){case "number":case "string":if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(e)){e= h(e);break}e=l?$(e):$(e,q);case "object":if(e.length===0)return;if(e.is||e.style)t=(e=$(e)).offset()}var v=$.isFunction(b.offset)&&b.offset(q,e)||b.offset;$.each(b.axis.split(""),function(a,c){var d="x"===c?"Left":"Top",m=d.toLowerCase(),g="scroll"+d,h=r[g](),n=p.max(q,c);t?(f[g]=t[m]+(l?0:h-r.offset()[m]),b.margin&&(f[g]-=parseInt(e.css("margin"+d),10)||0,f[g]-=parseInt(e.css("border"+d+"Width"),10)||0),f[g]+=v[m]||0,b.over[m]&&(f[g]+=e["x"===c?"width":"height"]()*b.over[m])):(d=e[m],f[g]=d.slice&& "%"===d.slice(-1)?parseFloat(d)/100*n:d);b.limit&&/^\d+$/.test(f[g])&&(f[g]=0>=f[g]?0:Math.min(f[g],n));!a&&1<b.axis.length&&(h===f[g]?f={}:u&&(k(b.onAfterFirst),f={}))});k(b.onAfter)}})};p.max=function(a,d){var b="x"===d?"Width":"Height",h="scroll"+b;if(!n(a))return a[h]-$(a)[b.toLowerCase()]();var b="client"+b,k=a.ownerDocument||a.document,l=k.documentElement,k=k.body;return Math.max(l[h],k[h])-Math.min(l[b],k[b])};$.Tween.propHooks.scrollLeft=$.Tween.propHooks.scrollTop={get:function(a){return $(a.elem)[a.prop]()}, set:function(a){var d=this.get(a);if(a.options.interrupt&&a._last&&a._last!==d)return $(a.elem).stop();var b=Math.round(a.now);d!==b&&($(a.elem)[a.prop](b),a._last=this.get(a))}};return p}); \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryUI.js b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryUI.js new file mode 100644 index 0000000000000000000000000000000000000000..83251acf8e00a32e1354513fec8a1fdbc9d0b821 --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryUI.js @@ -0,0 +1,13 @@ +/*! jQuery UI - v1.12.1 - 2016-09-14 +* http://jqueryui.com +* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("<div id='"+this._mainDivId+"' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>"))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(m.inline?m.dpDiv.parent()[0]:m.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>o;o++)for(i in n[o])s=n[o][i],n[o].hasOwnProperty(i)&&void 0!==s&&(e[i]=t.isPlainObject(s)?t.isPlainObject(e[i])?t.widget.extend({},e[i],s):t.widget.extend({},s):s);return e},t.widget.bridge=function(e,i){var s=i.prototype.widgetFullName||e;t.fn[e]=function(n){var o="string"==typeof n,a=l.call(arguments,1),r=this;return o?this.length||"instance"!==n?this.each(function(){var i,o=t.data(this,s);return"instance"===n?(r=o,!1):o?t.isFunction(o[n])&&"_"!==n.charAt(0)?(i=o[n].apply(o,a),i!==o&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+n+"'")}):r=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,s);e?(e.option(n||{}),e._init&&e._init()):t.data(this,s,new i(n,this))})),r}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+o.eventNamespace,c=h[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.width<e.element[0].scrollWidth,o="scroll"===s||"auto"===s&&e.height<e.element[0].scrollHeight;return{width:o?t.position.scrollbarWidth():0,height:n?t.position.scrollbarWidth():0}},getWithinInfo:function(e){var i=t(e||window),s=t.isWindow(i[0]),n=!!i[0]&&9===i[0].nodeType,o=!s&&!n;return{element:i,isWindow:s,isDocument:n,offset:o?t(e).offset():{left:0,top:0},scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:i.outerWidth(),height:i.outerHeight()}}},t.fn.position=function(n){if(!n||!n.of)return d.apply(this,arguments);n=t.extend({},n);var u,p,f,g,m,_,v=t(n.of),b=t.position.getWithinInfo(n.within),y=t.position.getScrollInfo(b),w=(n.collision||"flip").split(" "),k={};return _=s(v),v[0].preventDefault&&(n.at="left top"),p=_.width,f=_.height,g=_.offset,m=t.extend({},g),t.each(["my","at"],function(){var t,e,i=(n[this]||"").split(" ");1===i.length&&(i=r.test(i[0])?i.concat(["center"]):h.test(i[0])?["center"].concat(i):["center","center"]),i[0]=r.test(i[0])?i[0]:"center",i[1]=h.test(i[1])?i[1]:"center",t=l.exec(i[0]),e=l.exec(i[1]),k[this]=[t?t[0]:0,e?e[0]:0],n[this]=[c.exec(i[0])[0],c.exec(i[1])[0]]}),1===w.length&&(w[1]=w[0]),"right"===n.at[0]?m.left+=p:"center"===n.at[0]&&(m.left+=p/2),"bottom"===n.at[1]?m.top+=f:"center"===n.at[1]&&(m.top+=f/2),u=e(k.at,p,f),m.left+=u[0],m.top+=u[1],this.each(function(){var s,r,h=t(this),l=h.outerWidth(),c=h.outerHeight(),d=i(this,"marginLeft"),_=i(this,"marginTop"),x=l+d+i(this,"marginRight")+y.width,C=c+_+i(this,"marginBottom")+y.height,D=t.extend({},m),I=e(k.my,h.outerWidth(),h.outerHeight());"right"===n.my[0]?D.left-=l:"center"===n.my[0]&&(D.left-=l/2),"bottom"===n.my[1]?D.top-=c:"center"===n.my[1]&&(D.top-=c/2),D.left+=I[0],D.top+=I[1],s={marginLeft:d,marginTop:_},t.each(["left","top"],function(e,i){t.ui.position[w[e]]&&t.ui.position[w[e]][i](D,{targetWidth:p,targetHeight:f,elemWidth:l,elemHeight:c,collisionPosition:s,collisionWidth:x,collisionHeight:C,offset:[u[0]+I[0],u[1]+I[1]],my:n.my,at:n.at,within:b,elem:h})}),n.using&&(r=function(t){var e=g.left-D.left,i=e+p-l,s=g.top-D.top,r=s+f-c,u={target:{element:v,left:g.left,top:g.top,width:p,height:f},element:{element:h,left:D.left,top:D.top,width:l,height:c},horizontal:0>i?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};l>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-r-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-r-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}});var c="ui-effects-",u="ui-effects-style",d="ui-effects-animated",p=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("<p>")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(p),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(p.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(d)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(c+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(c+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(u,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(u)||"",t.removeData(u)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(c+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=c+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){r.removeData(d),t.effects.cleanUp(r),"hide"===s.mode&&r.hide(),a()}function a(){t.isFunction(h)&&h.call(r[0]),t.isFunction(e)&&e()}var r=t(this);s.mode=c.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(r[l](),a()):n.call(r[0],s,i):(r.is(":hidden")?"hide"===l:"show"===l)?(r[l](),a()):n.call(r[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,r=a||"fx",h=s.complete,l=s.mode,c=[],u=function(e){var i=t(this),s=t.effects.mode(i,l)||o;i.data(d,!0),c.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?l?this[l](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(u).each(i):this.queue(r,u).queue(r,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n) + }}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("<div class='ui-effects-transfer'></div>").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var f=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,h=/([0-9]+)%/.exec(r),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,h=2*(e.times||5)+(r?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);h>u;u++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,d,h+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,h=2*r+1,l=Math.round(e.duration/h),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,l,e.easing);r>s;s++)n.animate(p,l,e.easing).animate(f,l,e.easing);n.animate(p,l,e.easing).animate(d,l/2,e.easing).queue(i),t.effects.unshift(n,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[l],d[l]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[h][1]]=d.clip[a[h][0]],"show"===r&&(o.cssClip(d.clip),o.css(l,d[l]),d.clip=s,d[l]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var f;t.uiBackCompat!==!1&&(f=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})),t.ui.focusable=function(i,s){var n,o,a,r,h,l=i.nodeName.toLowerCase();return"area"===l?(n=i.parentNode,o=n.name,i.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap='#"+o+"']"),a.length>0&&a.is(":visible")):!1):(/^(input|select|textarea|button|object)$/.test(l)?(r=!i.disabled,r&&(h=t(i).closest("fieldset")[0],h&&(r=!h.disabled))):r="a"===l?i.href||s:s,r&&t(i).is(":visible")&&e(t(i)))},t.extend(t.expr[":"],{focusable:function(e){return t.ui.focusable(e,null!=t.attr(e,"tabindex"))}}),t.ui.focusable,t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},"1.7"===t.fn.jquery.substring(0,3)&&(t.each(["Width","Height"],function(e,i){function s(e,i,s,o){return t.each(n,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),o&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(e){return void 0===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,s(this,e)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,s(this,e,!0,n)+"px")})}}),t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.ui.escapeSelector=function(){var t=/([!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var e,i,s,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),s=this.attr("id"),s&&(e=this.eq(0).parents().last(),o=e.add(e.length?e.siblings():this.siblings()),i="label[for='"+t.ui.escapeSelector(s)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||i>=0)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e,i,s=this.options.icons;s&&(e=t("<span>"),this._addClass(e,"ui-accordion-header-icon","ui-icon "+s.header),e.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void 0)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i,s,n=this.options,o=this.active,a=t(e.currentTarget),r=a[0]===o[0],h=r&&n.collapsible,l=h?t():a.next(),c=o.next(),u={oldHeader:o,oldPanel:c,newHeader:h?t():a,newPanel:l};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,u)===!1||(n.active=h?!1:this.headers.index(a),this.active=r?t():a,this._toggle(u),this._removeClass(o,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=o.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),r||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),l=t.length&&(!e.length||t.index()<e.index()),c=this.options.animate||{},u=l&&c.down||c,d=function(){a._toggleComplete(i)};return"number"==typeof u&&(o=u),"string"==typeof u&&(n=u),n=n||u.easing||c.easing,o=o||u.duration||c.duration,e.length?t.length?(s=t.show().outerHeight(),e.animate(this.hideProps,{duration:o,easing:n,step:function(t,e){e.now=Math.round(t)}}),t.hide().animate(this.showProps,{duration:o,easing:n,complete:d,step:function(t,i){i.now=Math.round(t),"height"!==i.prop?"content-box"===h&&(r+=i.now):"content"!==a.options.heightStyle&&(i.now=Math.round(s-e.outerHeight()-r),r=0)}}),void 0):e.animate(this.hideProps,o,n,d):t.animate(this.showProps,o,n,d)},_toggleComplete:function(t){var e=t.oldPanel,i=e.prev();this._removeClass(e,"ui-accordion-content-active"),this._removeClass(i,"ui-accordion-header-active")._addClass(i,"ui-accordion-header-collapsed"),e.length&&(e.parent()[0].className=e.parent()[0].className),this._trigger("activate",null,t)}}),t.ui.safeActiveElement=function(t){var e;try{e=t.activeElement}catch(i){e=t.body}return e||(e=t.body),e.nodeName||(e=t.body),e},t.widget("ui.menu",{version:"1.12.1",defaultElement:"<ul>",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("<span>").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()<this.element.prop("scrollHeight")},select:function(e){this.active=this.active||t(e.target).closest(".ui-menu-item");var i={item:this.active};this.active.has(".ui-menu").length||this.collapseAll(e,!0),this._trigger("select",e,i)},_filterMenuItems:function(e){var i=e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&"),s=RegExp("^"+i,"i");return this.activeMenu.find(this.options.items).filter(".ui-menu-item").filter(function(){return s.test(t.trim(t(this).children(".ui-menu-item-wrapper").text()))})}}),t.widget("ui.autocomplete",{version:"1.12.1",defaultElement:"<input>",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n; + this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("<ul>").appendTo(this._appendTo()).menu({role:null}).hide().menu("instance"),this._addClass(this.menu.element,"ui-autocomplete","ui-front"),this._on(this.menu.element,{mousedown:function(e){e.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,this.element[0]!==t.ui.safeActiveElement(this.document[0])&&this.element.trigger("focus")})},menufocus:function(e,i){var s,n;return this.isNewMenu&&(this.isNewMenu=!1,e.originalEvent&&/^mouse/.test(e.originalEvent.type))?(this.menu.blur(),this.document.one("mousemove",function(){t(e.target).trigger(e.originalEvent)}),void 0):(n=i.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",e,{item:n})&&e.originalEvent&&/^key/.test(e.originalEvent.type)&&this._value(n.value),s=i.item.attr("aria-label")||n.value,s&&t.trim(s).length&&(this.liveRegion.children().hide(),t("<div>").text(s).appendTo(this.liveRegion)),void 0)},menuselect:function(e,i){var s=i.item.data("ui-autocomplete-item"),n=this.previous;this.element[0]!==t.ui.safeActiveElement(this.document[0])&&(this.element.trigger("focus"),this.previous=n,this._delay(function(){this.previous=n,this.selectedItem=s})),!1!==this._trigger("select",e,{item:s})&&this._value(s.value),this.term=this._value(),this.close(e),this.selectedItem=s}}),this.liveRegion=t("<div>",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(t,e){this._super(t,e),"source"===t&&this._initSource(),"appendTo"===t&&this.menu.element.appendTo(this._appendTo()),"disabled"===t&&e&&this.xhr&&this.xhr.abort()},_isEventTargetInWidget:function(e){var i=this.menu.element[0];return e.target===this.element[0]||e.target===i||t.contains(i,e.target)},_closeOnClickOutside:function(t){this._isEventTargetInWidget(t)||this.close()},_appendTo:function(){var e=this.options.appendTo;return e&&(e=e.jquery||e.nodeType?t(e):this.document.find(e).eq(0)),e&&e[0]||(e=this.element.closest(".ui-front, dialog")),e.length||(e=this.document[0].body),e},_initSource:function(){var e,i,s=this;t.isArray(this.options.source)?(e=this.options.source,this.source=function(i,s){s(t.ui.autocomplete.filter(e,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(e,n){s.xhr&&s.xhr.abort(),s.xhr=t.ajax({url:i,data:e,dataType:"json",success:function(t){n(t)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(t){clearTimeout(this.searching),this.searching=this._delay(function(){var e=this.term===this._value(),i=this.menu.element.is(":visible"),s=t.altKey||t.ctrlKey||t.metaKey||t.shiftKey;(!e||e&&!i&&!s)&&(this.selectedItem=null,this.search(null,t))},this.options.delay)},search:function(t,e){return t=null!=t?t:this._value(),this.term=this._value(),t.length<this.options.minLength?this.close(e):this._trigger("search",e)!==!1?this._search(t):void 0},_search:function(t){this.pending++,this._addClass("ui-autocomplete-loading"),this.cancelSearch=!1,this.source({term:t},this._response())},_response:function(){var e=++this.requestIndex;return t.proxy(function(t){e===this.requestIndex&&this.__response(t),this.pending--,this.pending||this._removeClass("ui-autocomplete-loading")},this)},__response:function(t){t&&(t=this._normalize(t)),this._trigger("response",null,{content:t}),!this.options.disabled&&t&&t.length&&!this.cancelSearch?(this._suggest(t),this._trigger("open")):this._close()},close:function(t){this.cancelSearch=!0,this._close(t)},_close:function(t){this._off(this.document,"mousedown"),this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.blur(),this.isNewMenu=!0,this._trigger("close",t))},_change:function(t){this.previous!==this._value()&&this._trigger("change",t,{item:this.selectedItem})},_normalize:function(e){return e.length&&e[0].label&&e[0].value?e:t.map(e,function(e){return"string"==typeof e?{label:e,value:e}:t.extend({},e,{label:e.label||e.value,value:e.value||e.label})})},_suggest:function(e){var i=this.menu.element.empty();this._renderMenu(i,e),this.isNewMenu=!0,this.menu.refresh(),i.show(),this._resizeMenu(),i.position(t.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next(),this._on(this.document,{mousedown:"_closeOnClickOutside"})},_resizeMenu:function(){var t=this.menu.element;t.outerWidth(Math.max(t.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(e,i){var s=this;t.each(i,function(t,i){s._renderItemData(e,i)})},_renderItemData:function(t,e){return this._renderItem(t,e).data("ui-autocomplete-item",e)},_renderItem:function(e,i){return t("<li>").append(t("<div>").text(i.label)).appendTo(e)},_move:function(t,e){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(t)||this.menu.isLastItem()&&/^next/.test(t)?(this.isMultiLine||this._value(this.term),this.menu.blur(),void 0):(this.menu[t](e),void 0):(this.search(null,e),void 0)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(t,e){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(t,e),e.preventDefault())},_isContentEditable:function(t){if(!t.length)return!1;var e=t.prop("contentEditable");return"inherit"===e?this._isContentEditable(t.parent()):"true"===e}}),t.extend(t.ui.autocomplete,{escapeRegex:function(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(e,i){var s=RegExp(t.ui.autocomplete.escapeRegex(i),"i");return t.grep(e,function(t){return s.test(t.label||t.value||t)})}}),t.widget("ui.autocomplete",t.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(t){return t+(t>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var i;this._superApply(arguments),this.options.disabled||this.cancelSearch||(i=e&&e.length?this.options.messages.results(e.length):this.options.messages.noResults,this.liveRegion.children().hide(),t("<div>").text(i).appendTo(this.liveRegion))}}),t.ui.autocomplete;var g=/ui-corner-([a-z]){2,6}/g;t.widget("ui.controlgroup",{version:"1.12.1",defaultElement:"<div>",options:{direction:"horizontal",disabled:null,onlyVisible:!0,items:{button:"input[type=button], input[type=submit], input[type=reset], button, a",controlgroupLabel:".ui-controlgroup-label",checkboxradio:"input[type='checkbox'], input[type='radio']",selectmenu:"select",spinner:".ui-spinner-input"}},_create:function(){this._enhance()},_enhance:function(){this.element.attr("role","toolbar"),this.refresh()},_destroy:function(){this._callChildMethod("destroy"),this.childWidgets.removeData("ui-controlgroup-data"),this.element.removeAttr("role"),this.options.items.controlgroupLabel&&this.element.find(this.options.items.controlgroupLabel).find(".ui-controlgroup-label-contents").contents().unwrap()},_initWidgets:function(){var e=this,i=[];t.each(this.options.items,function(s,n){var o,a={};return n?"controlgroupLabel"===s?(o=e.element.find(n),o.each(function(){var e=t(this);e.children(".ui-controlgroup-label-contents").length||e.contents().wrapAll("<span class='ui-controlgroup-label-contents'></span>")}),e._addClass(o,null,"ui-widget ui-widget-content ui-state-default"),i=i.concat(o.get()),void 0):(t.fn[s]&&(a=e["_"+s+"Options"]?e["_"+s+"Options"]("middle"):{classes:{}},e.element.find(n).each(function(){var n=t(this),o=n[s]("instance"),r=t.widget.extend({},a);if("button"!==s||!n.parent(".ui-spinner").length){o||(o=n[s]()[s]("instance")),o&&(r.classes=e._resolveClassesValues(r.classes,o)),n[s](r);var h=n[s]("widget");t.data(h[0],"ui-controlgroup-data",o?o:n[s]("instance")),i.push(h[0])}})),void 0):void 0}),this.childWidgets=t(t.unique(i)),this._addClass(this.childWidgets,"ui-controlgroup-item")},_callChildMethod:function(e){this.childWidgets.each(function(){var i=t(this),s=i.data("ui-controlgroup-data");s&&s[e]&&s[e]()})},_updateCornerClass:function(t,e){var i="ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all",s=this._buildSimpleOptions(e,"label").classes.label;this._removeClass(t,null,i),this._addClass(t,null,s)},_buildSimpleOptions:function(t,e){var i="vertical"===this.options.direction,s={classes:{}};return s.classes[e]={middle:"",first:"ui-corner-"+(i?"top":"left"),last:"ui-corner-"+(i?"bottom":"right"),only:"ui-corner-all"}[t],s},_spinnerOptions:function(t){var e=this._buildSimpleOptions(t,"ui-spinner");return e.classes["ui-spinner-up"]="",e.classes["ui-spinner-down"]="",e},_buttonOptions:function(t){return this._buildSimpleOptions(t,"ui-button")},_checkboxradioOptions:function(t){return this._buildSimpleOptions(t,"ui-checkboxradio-label")},_selectmenuOptions:function(t){var e="vertical"===this.options.direction;return{width:e?"auto":!1,classes:{middle:{"ui-selectmenu-button-open":"","ui-selectmenu-button-closed":""},first:{"ui-selectmenu-button-open":"ui-corner-"+(e?"top":"tl"),"ui-selectmenu-button-closed":"ui-corner-"+(e?"top":"left")},last:{"ui-selectmenu-button-open":e?"":"ui-corner-tr","ui-selectmenu-button-closed":"ui-corner-"+(e?"bottom":"right")},only:{"ui-selectmenu-button-open":"ui-corner-top","ui-selectmenu-button-closed":"ui-corner-all"}}[t]}},_resolveClassesValues:function(e,i){var s={};return t.each(e,function(n){var o=i.options.classes[n]||"";o=t.trim(o.replace(g,"")),s[n]=(o+" "+e[n]).replace(/\s+/g," ")}),s},_setOption:function(t,e){return"direction"===t&&this._removeClass("ui-controlgroup-"+this.options.direction),this._super(t,e),"disabled"===t?(this._callChildMethod(e?"disable":"enable"),void 0):(this.refresh(),void 0)},refresh:function(){var e,i=this;this._addClass("ui-controlgroup ui-controlgroup-"+this.options.direction),"horizontal"===this.options.direction&&this._addClass(null,"ui-helper-clearfix"),this._initWidgets(),e=this.childWidgets,this.options.onlyVisible&&(e=e.filter(":visible")),e.length&&(t.each(["first","last"],function(t,s){var n=e[s]().data("ui-controlgroup-data");if(n&&i["_"+n.widgetName+"Options"]){var o=i["_"+n.widgetName+"Options"](1===e.length?"only":s);o.classes=i._resolveClassesValues(o.classes,n),n.element[n.widgetName](o)}else i._updateCornerClass(e[s](),s)}),this._callChildMethod("refresh"))}}),t.widget("ui.checkboxradio",[t.ui.formResetMixin,{version:"1.12.1",options:{disabled:null,label:null,icon:!0,classes:{"ui-checkboxradio-label":"ui-corner-all","ui-checkboxradio-icon":"ui-corner-all"}},_getCreateOptions:function(){var e,i,s=this,n=this._super()||{};return this._readType(),i=this.element.labels(),this.label=t(i[i.length-1]),this.label.length||t.error("No label found for checkboxradio widget"),this.originalLabel="",this.label.contents().not(this.element[0]).each(function(){s.originalLabel+=3===this.nodeType?t(this).text():this.outerHTML}),this.originalLabel&&(n.label=this.originalLabel),e=this.element[0].disabled,null!=e&&(n.disabled=e),n},_create:function(){var t=this.element[0].checked;this._bindFormResetHandler(),null==this.options.disabled&&(this.options.disabled=this.element[0].disabled),this._setOption("disabled",this.options.disabled),this._addClass("ui-checkboxradio","ui-helper-hidden-accessible"),this._addClass(this.label,"ui-checkboxradio-label","ui-button ui-widget"),"radio"===this.type&&this._addClass(this.label,"ui-checkboxradio-radio-label"),this.options.label&&this.options.label!==this.originalLabel?this._updateLabel():this.originalLabel&&(this.options.label=this.originalLabel),this._enhance(),t&&(this._addClass(this.label,"ui-checkboxradio-checked","ui-state-active"),this.icon&&this._addClass(this.icon,null,"ui-state-hover")),this._on({change:"_toggleClasses",focus:function(){this._addClass(this.label,null,"ui-state-focus ui-visual-focus")},blur:function(){this._removeClass(this.label,null,"ui-state-focus ui-visual-focus")}})},_readType:function(){var e=this.element[0].nodeName.toLowerCase();this.type=this.element[0].type,"input"===e&&/radio|checkbox/.test(this.type)||t.error("Can't create checkboxradio on element.nodeName="+e+" and element.type="+this.type)},_enhance:function(){this._updateIcon(this.element[0].checked)},widget:function(){return this.label},_getRadioGroup:function(){var e,i=this.element[0].name,s="input[name='"+t.ui.escapeSelector(i)+"']";return i?(e=this.form.length?t(this.form[0].elements).filter(s):t(s).filter(function(){return 0===t(this).form().length}),e.not(this.element)):t([])},_toggleClasses:function(){var e=this.element[0].checked;this._toggleClass(this.label,"ui-checkboxradio-checked","ui-state-active",e),this.options.icon&&"checkbox"===this.type&&this._toggleClass(this.icon,null,"ui-icon-check ui-state-checked",e)._toggleClass(this.icon,null,"ui-icon-blank",!e),"radio"===this.type&&this._getRadioGroup().each(function(){var e=t(this).checkboxradio("instance");e&&e._removeClass(e.label,"ui-checkboxradio-checked","ui-state-active")})},_destroy:function(){this._unbindFormResetHandler(),this.icon&&(this.icon.remove(),this.iconSpace.remove())},_setOption:function(t,e){return"label"!==t||e?(this._super(t,e),"disabled"===t?(this._toggleClass(this.label,null,"ui-state-disabled",e),this.element[0].disabled=e,void 0):(this.refresh(),void 0)):void 0},_updateIcon:function(e){var i="ui-icon ui-icon-background ";this.options.icon?(this.icon||(this.icon=t("<span>"),this.iconSpace=t("<span> </span>"),this._addClass(this.iconSpace,"ui-checkboxradio-icon-space")),"checkbox"===this.type?(i+=e?"ui-icon-check ui-state-checked":"ui-icon-blank",this._removeClass(this.icon,null,e?"ui-icon-blank":"ui-icon-check")):i+="ui-icon-blank",this._addClass(this.icon,"ui-checkboxradio-icon",i),e||this._removeClass(this.icon,null,"ui-icon-check ui-state-checked"),this.icon.prependTo(this.label).after(this.iconSpace)):void 0!==this.icon&&(this.icon.remove(),this.iconSpace.remove(),delete this.icon)},_updateLabel:function(){var t=this.label.contents().not(this.element[0]);this.icon&&(t=t.not(this.icon[0])),this.iconSpace&&(t=t.not(this.iconSpace[0])),t.remove(),this.label.append(this.options.label)},refresh:function(){var t=this.element[0].checked,e=this.element[0].disabled;this._updateIcon(t),this._toggleClass(this.label,"ui-checkboxradio-checked","ui-state-active",t),null!==this.options.label&&this._updateLabel(),e!==this.options.disabled&&this._setOptions({disabled:e})}}]),t.ui.checkboxradio,t.widget("ui.button",{version:"1.12.1",defaultElement:"<button>",options:{classes:{"ui-button":"ui-corner-all"},disabled:null,icon:null,iconPosition:"beginning",label:null,showLabel:!0},_getCreateOptions:function(){var t,e=this._super()||{};return this.isInput=this.element.is("input"),t=this.element[0].disabled,null!=t&&(e.disabled=t),this.originalLabel=this.isInput?this.element.val():this.element.html(),this.originalLabel&&(e.label=this.originalLabel),e},_create:function(){!this.option.showLabel&!this.options.icon&&(this.options.showLabel=!0),null==this.options.disabled&&(this.options.disabled=this.element[0].disabled||!1),this.hasTitle=!!this.element.attr("title"),this.options.label&&this.options.label!==this.originalLabel&&(this.isInput?this.element.val(this.options.label):this.element.html(this.options.label)),this._addClass("ui-button","ui-widget"),this._setOption("disabled",this.options.disabled),this._enhance(),this.element.is("a")&&this._on({keyup:function(e){e.keyCode===t.ui.keyCode.SPACE&&(e.preventDefault(),this.element[0].click?this.element[0].click():this.element.trigger("click"))}})},_enhance:function(){this.element.is("button")||this.element.attr("role","button"),this.options.icon&&(this._updateIcon("icon",this.options.icon),this._updateTooltip())},_updateTooltip:function(){this.title=this.element.attr("title"),this.options.showLabel||this.title||this.element.attr("title",this.options.label)},_updateIcon:function(e,i){var s="iconPosition"!==e,n=s?this.options.iconPosition:i,o="top"===n||"bottom"===n;this.icon?s&&this._removeClass(this.icon,null,this.options.icon):(this.icon=t("<span>"),this._addClass(this.icon,"ui-button-icon","ui-icon"),this.options.showLabel||this._addClass("ui-button-icon-only")),s&&this._addClass(this.icon,null,i),this._attachIcon(n),o?(this._addClass(this.icon,null,"ui-widget-icon-block"),this.iconSpace&&this.iconSpace.remove()):(this.iconSpace||(this.iconSpace=t("<span> </span>"),this._addClass(this.iconSpace,"ui-button-icon-space")),this._removeClass(this.icon,null,"ui-wiget-icon-block"),this._attachIconSpace(n))},_destroy:function(){this.element.removeAttr("role"),this.icon&&this.icon.remove(),this.iconSpace&&this.iconSpace.remove(),this.hasTitle||this.element.removeAttr("title")},_attachIconSpace:function(t){this.icon[/^(?:end|bottom)/.test(t)?"before":"after"](this.iconSpace)},_attachIcon:function(t){this.element[/^(?:end|bottom)/.test(t)?"append":"prepend"](this.icon)},_setOptions:function(t){var e=void 0===t.showLabel?this.options.showLabel:t.showLabel,i=void 0===t.icon?this.options.icon:t.icon;e||i||(t.showLabel=!0),this._super(t)},_setOption:function(t,e){"icon"===t&&(e?this._updateIcon(t,e):this.icon&&(this.icon.remove(),this.iconSpace&&this.iconSpace.remove())),"iconPosition"===t&&this._updateIcon(t,e),"showLabel"===t&&(this._toggleClass("ui-button-icon-only",null,!e),this._updateTooltip()),"label"===t&&(this.isInput?this.element.val(e):(this.element.html(e),this.icon&&(this._attachIcon(this.options.iconPosition),this._attachIconSpace(this.options.iconPosition)))),this._super(t,e),"disabled"===t&&(this._toggleClass(null,"ui-state-disabled",e),this.element[0].disabled=e,e&&this.element.blur())},refresh:function(){var t=this.element.is("input, button")?this.element[0].disabled:this.element.hasClass("ui-button-disabled");t!==this.options.disabled&&this._setOptions({disabled:t}),this._updateTooltip()}}),t.uiBackCompat!==!1&&(t.widget("ui.button",t.ui.button,{options:{text:!0,icons:{primary:null,secondary:null}},_create:function(){this.options.showLabel&&!this.options.text&&(this.options.showLabel=this.options.text),!this.options.showLabel&&this.options.text&&(this.options.text=this.options.showLabel),this.options.icon||!this.options.icons.primary&&!this.options.icons.secondary?this.options.icon&&(this.options.icons.primary=this.options.icon):this.options.icons.primary?this.options.icon=this.options.icons.primary:(this.options.icon=this.options.icons.secondary,this.options.iconPosition="end"),this._super()},_setOption:function(t,e){return"text"===t?(this._super("showLabel",e),void 0):("showLabel"===t&&(this.options.text=e),"icon"===t&&(this.options.icons.primary=e),"icons"===t&&(e.primary?(this._super("icon",e.primary),this._super("iconPosition","beginning")):e.secondary&&(this._super("icon",e.secondary),this._super("iconPosition","end"))),this._superApply(arguments),void 0)}}),t.fn.button=function(e){return function(){return!this.length||this.length&&"INPUT"!==this[0].tagName||this.length&&"INPUT"===this[0].tagName&&"checkbox"!==this.attr("type")&&"radio"!==this.attr("type")?e.apply(this,arguments):(t.ui.checkboxradio||t.error("Checkboxradio widget missing"),0===arguments.length?this.checkboxradio({icon:!1}):this.checkboxradio.apply(this,arguments))}}(t.fn.button),t.fn.buttonset=function(){return t.ui.controlgroup||t.error("Controlgroup widget missing"),"option"===arguments[0]&&"items"===arguments[1]&&arguments[2]?this.controlgroup.apply(this,[arguments[0],"items.button",arguments[2]]):"option"===arguments[0]&&"items"===arguments[1]?this.controlgroup.apply(this,[arguments[0],"items.button"]):("object"==typeof arguments[0]&&arguments[0].items&&(arguments[0].items={button:arguments[0].items}),this.controlgroup.apply(this,arguments))}),t.ui.button,t.extend(t.ui,{datepicker:{version:"1.12.1"}});var m;t.extend(s.prototype,{markerClassName:"hasDatepicker",maxRows:4,_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(t){return a(this._defaults,t||{}),this},_attachDatepicker:function(e,i){var s,n,o;s=e.nodeName.toLowerCase(),n="div"===s||"span"===s,e.id||(this.uuid+=1,e.id="dp"+this.uuid),o=this._newInst(t(e),n),o.settings=t.extend({},i||{}),"input"===s?this._connectDatepicker(e,o):n&&this._inlineDatepicker(e,o)},_newInst:function(e,i){var s=e[0].id.replace(/([^A-Za-z0-9_\-])/g,"\\\\$1");return{id:s,input:e,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:i,dpDiv:i?n(t("<div class='"+this._inlineClass+" ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")):this.dpDiv}},_connectDatepicker:function(e,i){var s=t(e);i.append=t([]),i.trigger=t([]),s.hasClass(this.markerClassName)||(this._attachments(s,i),s.addClass(this.markerClassName).on("keydown",this._doKeyDown).on("keypress",this._doKeyPress).on("keyup",this._doKeyUp),this._autoSize(i),t.data(e,"datepicker",i),i.settings.disabled&&this._disableDatepicker(e))},_attachments:function(e,i){var s,n,o,a=this._get(i,"appendText"),r=this._get(i,"isRTL");i.append&&i.append.remove(),a&&(i.append=t("<span class='"+this._appendClass+"'>"+a+"</span>"),e[r?"before":"after"](i.append)),e.off("focus",this._showDatepicker),i.trigger&&i.trigger.remove(),s=this._get(i,"showOn"),("focus"===s||"both"===s)&&e.on("focus",this._showDatepicker),("button"===s||"both"===s)&&(n=this._get(i,"buttonText"),o=this._get(i,"buttonImage"),i.trigger=t(this._get(i,"buttonImageOnly")?t("<img/>").addClass(this._triggerClass).attr({src:o,alt:n,title:n}):t("<button type='button'></button>").addClass(this._triggerClass).html(o?t("<img/>").attr({src:o,alt:n,title:n}):n)),e[r?"before":"after"](i.trigger),i.trigger.on("click",function(){return t.datepicker._datepickerShowing&&t.datepicker._lastInput===e[0]?t.datepicker._hideDatepicker():t.datepicker._datepickerShowing&&t.datepicker._lastInput!==e[0]?(t.datepicker._hideDatepicker(),t.datepicker._showDatepicker(e[0])):t.datepicker._showDatepicker(e[0]),!1}))},_autoSize:function(t){if(this._get(t,"autoSize")&&!t.inline){var e,i,s,n,o=new Date(2009,11,20),a=this._get(t,"dateFormat");a.match(/[DM]/)&&(e=function(t){for(i=0,s=0,n=0;t.length>n;n++)t[n].length>i&&(i=t[n].length,s=n);return s},o.setMonth(e(this._get(t,a.match(/MM/)?"monthNames":"monthNamesShort"))),o.setDate(e(this._get(t,a.match(/DD/)?"dayNames":"dayNamesShort"))+20-o.getDay())),t.input.attr("size",this._formatDate(t,o).length)}},_inlineDatepicker:function(e,i){var s=t(e);s.hasClass(this.markerClassName)||(s.addClass(this.markerClassName).append(i.dpDiv),t.data(e,"datepicker",i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(e),i.dpDiv.css("display","block"))},_dialogDatepicker:function(e,i,s,n,o){var r,h,l,c,u,d=this._dialogInst;return d||(this.uuid+=1,r="dp"+this.uuid,this._dialogInput=t("<input type='text' id='"+r+"' style='position: absolute; top: -100px; width: 0px;'/>"),this._dialogInput.on("keydown",this._doKeyDown),t("body").append(this._dialogInput),d=this._dialogInst=this._newInst(this._dialogInput,!1),d.settings={},t.data(this._dialogInput[0],"datepicker",d)),a(d.settings,n||{}),i=i&&i.constructor===Date?this._formatDate(d,i):i,this._dialogInput.val(i),this._pos=o?o.length?o:[o.pageX,o.pageY]:null,this._pos||(h=document.documentElement.clientWidth,l=document.documentElement.clientHeight,c=document.documentElement.scrollLeft||document.body.scrollLeft,u=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[h/2-100+c,l/2-150+u]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),d.settings.onSelect=s,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),t.blockUI&&t.blockUI(this.dpDiv),t.data(this._dialogInput[0],"datepicker",d),this},_destroyDatepicker:function(e){var i,s=t(e),n=t.data(e,"datepicker");s.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),t.removeData(e,"datepicker"),"input"===i?(n.append.remove(),n.trigger.remove(),s.removeClass(this.markerClassName).off("focus",this._showDatepicker).off("keydown",this._doKeyDown).off("keypress",this._doKeyPress).off("keyup",this._doKeyUp)):("div"===i||"span"===i)&&s.removeClass(this.markerClassName).empty(),m===n&&(m=null))},_enableDatepicker:function(e){var i,s,n=t(e),o=t.data(e,"datepicker");n.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),"input"===i?(e.disabled=!1,o.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().removeClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=t.map(this._disabledInputs,function(t){return t===e?null:t}))},_disableDatepicker:function(e){var i,s,n=t(e),o=t.data(e,"datepicker");n.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),"input"===i?(e.disabled=!0,o.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().addClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=t.map(this._disabledInputs,function(t){return t===e?null:t}),this._disabledInputs[this._disabledInputs.length]=e)},_isDisabledDatepicker:function(t){if(!t)return!1;for(var e=0;this._disabledInputs.length>e;e++)if(this._disabledInputs[e]===t)return!0;return!1},_getInst:function(e){try{return t.data(e,"datepicker")}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(e,i,s){var n,o,r,h,l=this._getInst(e);return 2===arguments.length&&"string"==typeof i?"defaults"===i?t.extend({},t.datepicker._defaults):l?"all"===i?t.extend({},l.settings):this._get(l,i):null:(n=i||{},"string"==typeof i&&(n={},n[i]=s),l&&(this._curInst===l&&this._hideDatepicker(),o=this._getDateDatepicker(e,!0),r=this._getMinMaxDate(l,"min"),h=this._getMinMaxDate(l,"max"),a(l.settings,n),null!==r&&void 0!==n.dateFormat&&void 0===n.minDate&&(l.settings.minDate=this._formatDate(l,r)),null!==h&&void 0!==n.dateFormat&&void 0===n.maxDate&&(l.settings.maxDate=this._formatDate(l,h)),"disabled"in n&&(n.disabled?this._disableDatepicker(e):this._enableDatepicker(e)),this._attachments(t(e),l),this._autoSize(l),this._setDate(l,o),this._updateAlternate(l),this._updateDatepicker(l)),void 0)},_changeDatepicker:function(t,e,i){this._optionDatepicker(t,e,i)},_refreshDatepicker:function(t){var e=this._getInst(t);e&&this._updateDatepicker(e)},_setDateDatepicker:function(t,e){var i=this._getInst(t);i&&(this._setDate(i,e),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(t,e){var i=this._getInst(t);return i&&!i.inline&&this._setDateFromField(i,e),i?this._getDate(i):null},_doKeyDown:function(e){var i,s,n,o=t.datepicker._getInst(e.target),a=!0,r=o.dpDiv.is(".ui-datepicker-rtl");if(o._keyEvent=!0,t.datepicker._datepickerShowing)switch(e.keyCode){case 9:t.datepicker._hideDatepicker(),a=!1;break;case 13:return n=t("td."+t.datepicker._dayOverClass+":not(."+t.datepicker._currentClass+")",o.dpDiv),n[0]&&t.datepicker._selectDay(e.target,o.selectedMonth,o.selectedYear,n[0]),i=t.datepicker._get(o,"onSelect"),i?(s=t.datepicker._formatDate(o),i.apply(o.input?o.input[0]:null,[s,o])):t.datepicker._hideDatepicker(),!1;case 27:t.datepicker._hideDatepicker();break;case 33:t.datepicker._adjustDate(e.target,e.ctrlKey?-t.datepicker._get(o,"stepBigMonths"):-t.datepicker._get(o,"stepMonths"),"M");break;case 34:t.datepicker._adjustDate(e.target,e.ctrlKey?+t.datepicker._get(o,"stepBigMonths"):+t.datepicker._get(o,"stepMonths"),"M");break;case 35:(e.ctrlKey||e.metaKey)&&t.datepicker._clearDate(e.target),a=e.ctrlKey||e.metaKey;break;case 36:(e.ctrlKey||e.metaKey)&&t.datepicker._gotoToday(e.target),a=e.ctrlKey||e.metaKey;break;case 37:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,r?1:-1,"D"),a=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&t.datepicker._adjustDate(e.target,e.ctrlKey?-t.datepicker._get(o,"stepBigMonths"):-t.datepicker._get(o,"stepMonths"),"M");break;case 38:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,-7,"D"),a=e.ctrlKey||e.metaKey;break;case 39:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,r?-1:1,"D"),a=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&t.datepicker._adjustDate(e.target,e.ctrlKey?+t.datepicker._get(o,"stepBigMonths"):+t.datepicker._get(o,"stepMonths"),"M");break;case 40:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,7,"D"),a=e.ctrlKey||e.metaKey;break;default:a=!1}else 36===e.keyCode&&e.ctrlKey?t.datepicker._showDatepicker(this):a=!1;a&&(e.preventDefault(),e.stopPropagation())},_doKeyPress:function(e){var i,s,n=t.datepicker._getInst(e.target);return t.datepicker._get(n,"constrainInput")?(i=t.datepicker._possibleChars(t.datepicker._get(n,"dateFormat")),s=String.fromCharCode(null==e.charCode?e.keyCode:e.charCode),e.ctrlKey||e.metaKey||" ">s||!i||i.indexOf(s)>-1):void 0},_doKeyUp:function(e){var i,s=t.datepicker._getInst(e.target);if(s.input.val()!==s.lastVal)try{i=t.datepicker.parseDate(t.datepicker._get(s,"dateFormat"),s.input?s.input.val():null,t.datepicker._getFormatConfig(s)),i&&(t.datepicker._setDateFromField(s),t.datepicker._updateAlternate(s),t.datepicker._updateDatepicker(s))}catch(n){}return!0},_showDatepicker:function(e){if(e=e.target||e,"input"!==e.nodeName.toLowerCase()&&(e=t("input",e.parentNode)[0]),!t.datepicker._isDisabledDatepicker(e)&&t.datepicker._lastInput!==e){var s,n,o,r,h,l,c;s=t.datepicker._getInst(e),t.datepicker._curInst&&t.datepicker._curInst!==s&&(t.datepicker._curInst.dpDiv.stop(!0,!0),s&&t.datepicker._datepickerShowing&&t.datepicker._hideDatepicker(t.datepicker._curInst.input[0])),n=t.datepicker._get(s,"beforeShow"),o=n?n.apply(e,[e,s]):{},o!==!1&&(a(s.settings,o),s.lastVal=null,t.datepicker._lastInput=e,t.datepicker._setDateFromField(s),t.datepicker._inDialog&&(e.value=""),t.datepicker._pos||(t.datepicker._pos=t.datepicker._findPos(e),t.datepicker._pos[1]+=e.offsetHeight),r=!1,t(e).parents().each(function(){return r|="fixed"===t(this).css("position"),!r}),h={left:t.datepicker._pos[0],top:t.datepicker._pos[1]},t.datepicker._pos=null,s.dpDiv.empty(),s.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),t.datepicker._updateDatepicker(s),h=t.datepicker._checkOffset(s,h,r),s.dpDiv.css({position:t.datepicker._inDialog&&t.blockUI?"static":r?"fixed":"absolute",display:"none",left:h.left+"px",top:h.top+"px"}),s.inline||(l=t.datepicker._get(s,"showAnim"),c=t.datepicker._get(s,"duration"),s.dpDiv.css("z-index",i(t(e))+1),t.datepicker._datepickerShowing=!0,t.effects&&t.effects.effect[l]?s.dpDiv.show(l,t.datepicker._get(s,"showOptions"),c):s.dpDiv[l||"show"](l?c:null),t.datepicker._shouldFocusInput(s)&&s.input.trigger("focus"),t.datepicker._curInst=s)) + }},_updateDatepicker:function(e){this.maxRows=4,m=e,e.dpDiv.empty().append(this._generateHTML(e)),this._attachHandlers(e);var i,s=this._getNumberOfMonths(e),n=s[1],a=17,r=e.dpDiv.find("."+this._dayOverClass+" a");r.length>0&&o.apply(r.get(0)),e.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),n>1&&e.dpDiv.addClass("ui-datepicker-multi-"+n).css("width",a*n+"em"),e.dpDiv[(1!==s[0]||1!==s[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),e.dpDiv[(this._get(e,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),e===t.datepicker._curInst&&t.datepicker._datepickerShowing&&t.datepicker._shouldFocusInput(e)&&e.input.trigger("focus"),e.yearshtml&&(i=e.yearshtml,setTimeout(function(){i===e.yearshtml&&e.yearshtml&&e.dpDiv.find("select.ui-datepicker-year:first").replaceWith(e.yearshtml),i=e.yearshtml=null},0))},_shouldFocusInput:function(t){return t.input&&t.input.is(":visible")&&!t.input.is(":disabled")&&!t.input.is(":focus")},_checkOffset:function(e,i,s){var n=e.dpDiv.outerWidth(),o=e.dpDiv.outerHeight(),a=e.input?e.input.outerWidth():0,r=e.input?e.input.outerHeight():0,h=document.documentElement.clientWidth+(s?0:t(document).scrollLeft()),l=document.documentElement.clientHeight+(s?0:t(document).scrollTop());return i.left-=this._get(e,"isRTL")?n-a:0,i.left-=s&&i.left===e.input.offset().left?t(document).scrollLeft():0,i.top-=s&&i.top===e.input.offset().top+r?t(document).scrollTop():0,i.left-=Math.min(i.left,i.left+n>h&&h>n?Math.abs(i.left+n-h):0),i.top-=Math.min(i.top,i.top+o>l&&l>o?Math.abs(o+r):0),i},_findPos:function(e){for(var i,s=this._getInst(e),n=this._get(s,"isRTL");e&&("hidden"===e.type||1!==e.nodeType||t.expr.filters.hidden(e));)e=e[n?"previousSibling":"nextSibling"];return i=t(e).offset(),[i.left,i.top]},_hideDatepicker:function(e){var i,s,n,o,a=this._curInst;!a||e&&a!==t.data(e,"datepicker")||this._datepickerShowing&&(i=this._get(a,"showAnim"),s=this._get(a,"duration"),n=function(){t.datepicker._tidyDialog(a)},t.effects&&(t.effects.effect[i]||t.effects[i])?a.dpDiv.hide(i,t.datepicker._get(a,"showOptions"),s,n):a.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?s:null,n),i||n(),this._datepickerShowing=!1,o=this._get(a,"onClose"),o&&o.apply(a.input?a.input[0]:null,[a.input?a.input.val():"",a]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),t.blockUI&&(t.unblockUI(),t("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(t){t.dpDiv.removeClass(this._dialogClass).off(".ui-datepicker-calendar")},_checkExternalClick:function(e){if(t.datepicker._curInst){var i=t(e.target),s=t.datepicker._getInst(i[0]);(i[0].id!==t.datepicker._mainDivId&&0===i.parents("#"+t.datepicker._mainDivId).length&&!i.hasClass(t.datepicker.markerClassName)&&!i.closest("."+t.datepicker._triggerClass).length&&t.datepicker._datepickerShowing&&(!t.datepicker._inDialog||!t.blockUI)||i.hasClass(t.datepicker.markerClassName)&&t.datepicker._curInst!==s)&&t.datepicker._hideDatepicker()}},_adjustDate:function(e,i,s){var n=t(e),o=this._getInst(n[0]);this._isDisabledDatepicker(n[0])||(this._adjustInstDate(o,i+("M"===s?this._get(o,"showCurrentAtPos"):0),s),this._updateDatepicker(o))},_gotoToday:function(e){var i,s=t(e),n=this._getInst(s[0]);this._get(n,"gotoCurrent")&&n.currentDay?(n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear):(i=new Date,n.selectedDay=i.getDate(),n.drawMonth=n.selectedMonth=i.getMonth(),n.drawYear=n.selectedYear=i.getFullYear()),this._notifyChange(n),this._adjustDate(s)},_selectMonthYear:function(e,i,s){var n=t(e),o=this._getInst(n[0]);o["selected"+("M"===s?"Month":"Year")]=o["draw"+("M"===s?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(o),this._adjustDate(n)},_selectDay:function(e,i,s,n){var o,a=t(e);t(n).hasClass(this._unselectableClass)||this._isDisabledDatepicker(a[0])||(o=this._getInst(a[0]),o.selectedDay=o.currentDay=t("a",n).html(),o.selectedMonth=o.currentMonth=i,o.selectedYear=o.currentYear=s,this._selectDate(e,this._formatDate(o,o.currentDay,o.currentMonth,o.currentYear)))},_clearDate:function(e){var i=t(e);this._selectDate(i,"")},_selectDate:function(e,i){var s,n=t(e),o=this._getInst(n[0]);i=null!=i?i:this._formatDate(o),o.input&&o.input.val(i),this._updateAlternate(o),s=this._get(o,"onSelect"),s?s.apply(o.input?o.input[0]:null,[i,o]):o.input&&o.input.trigger("change"),o.inline?this._updateDatepicker(o):(this._hideDatepicker(),this._lastInput=o.input[0],"object"!=typeof o.input[0]&&o.input.trigger("focus"),this._lastInput=null)},_updateAlternate:function(e){var i,s,n,o=this._get(e,"altField");o&&(i=this._get(e,"altFormat")||this._get(e,"dateFormat"),s=this._getDate(e),n=this.formatDate(i,s,this._getFormatConfig(e)),t(o).val(n))},noWeekends:function(t){var e=t.getDay();return[e>0&&6>e,""]},iso8601Week:function(t){var e,i=new Date(t.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),e=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((e-i)/864e5)/7)+1},parseDate:function(e,i,s){if(null==e||null==i)throw"Invalid arguments";if(i="object"==typeof i?""+i:i+"",""===i)return null;var n,o,a,r,h=0,l=(s?s.shortYearCutoff:null)||this._defaults.shortYearCutoff,c="string"!=typeof l?l:(new Date).getFullYear()%100+parseInt(l,10),u=(s?s.dayNamesShort:null)||this._defaults.dayNamesShort,d=(s?s.dayNames:null)||this._defaults.dayNames,p=(s?s.monthNamesShort:null)||this._defaults.monthNamesShort,f=(s?s.monthNames:null)||this._defaults.monthNames,g=-1,m=-1,_=-1,v=-1,b=!1,y=function(t){var i=e.length>n+1&&e.charAt(n+1)===t;return i&&n++,i},w=function(t){var e=y(t),s="@"===t?14:"!"===t?20:"y"===t&&e?4:"o"===t?3:2,n="y"===t?s:1,o=RegExp("^\\d{"+n+","+s+"}"),a=i.substring(h).match(o);if(!a)throw"Missing number at position "+h;return h+=a[0].length,parseInt(a[0],10)},k=function(e,s,n){var o=-1,a=t.map(y(e)?n:s,function(t,e){return[[e,t]]}).sort(function(t,e){return-(t[1].length-e[1].length)});if(t.each(a,function(t,e){var s=e[1];return i.substr(h,s.length).toLowerCase()===s.toLowerCase()?(o=e[0],h+=s.length,!1):void 0}),-1!==o)return o+1;throw"Unknown name at position "+h},x=function(){if(i.charAt(h)!==e.charAt(n))throw"Unexpected literal at position "+h;h++};for(n=0;e.length>n;n++)if(b)"'"!==e.charAt(n)||y("'")?x():b=!1;else switch(e.charAt(n)){case"d":_=w("d");break;case"D":k("D",u,d);break;case"o":v=w("o");break;case"m":m=w("m");break;case"M":m=k("M",p,f);break;case"y":g=w("y");break;case"@":r=new Date(w("@")),g=r.getFullYear(),m=r.getMonth()+1,_=r.getDate();break;case"!":r=new Date((w("!")-this._ticksTo1970)/1e4),g=r.getFullYear(),m=r.getMonth()+1,_=r.getDate();break;case"'":y("'")?x():b=!0;break;default:x()}if(i.length>h&&(a=i.substr(h),!/^\s+/.test(a)))throw"Extra/unparsed characters found in date: "+a;if(-1===g?g=(new Date).getFullYear():100>g&&(g+=(new Date).getFullYear()-(new Date).getFullYear()%100+(c>=g?0:-100)),v>-1)for(m=1,_=v;;){if(o=this._getDaysInMonth(g,m-1),o>=_)break;m++,_-=o}if(r=this._daylightSavingAdjust(new Date(g,m-1,_)),r.getFullYear()!==g||r.getMonth()+1!==m||r.getDate()!==_)throw"Invalid date";return r},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(t,e,i){if(!e)return"";var s,n=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,o=(i?i.dayNames:null)||this._defaults.dayNames,a=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,r=(i?i.monthNames:null)||this._defaults.monthNames,h=function(e){var i=t.length>s+1&&t.charAt(s+1)===e;return i&&s++,i},l=function(t,e,i){var s=""+e;if(h(t))for(;i>s.length;)s="0"+s;return s},c=function(t,e,i,s){return h(t)?s[e]:i[e]},u="",d=!1;if(e)for(s=0;t.length>s;s++)if(d)"'"!==t.charAt(s)||h("'")?u+=t.charAt(s):d=!1;else switch(t.charAt(s)){case"d":u+=l("d",e.getDate(),2);break;case"D":u+=c("D",e.getDay(),n,o);break;case"o":u+=l("o",Math.round((new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime()-new Date(e.getFullYear(),0,0).getTime())/864e5),3);break;case"m":u+=l("m",e.getMonth()+1,2);break;case"M":u+=c("M",e.getMonth(),a,r);break;case"y":u+=h("y")?e.getFullYear():(10>e.getFullYear()%100?"0":"")+e.getFullYear()%100;break;case"@":u+=e.getTime();break;case"!":u+=1e4*e.getTime()+this._ticksTo1970;break;case"'":h("'")?u+="'":d=!0;break;default:u+=t.charAt(s)}return u},_possibleChars:function(t){var e,i="",s=!1,n=function(i){var s=t.length>e+1&&t.charAt(e+1)===i;return s&&e++,s};for(e=0;t.length>e;e++)if(s)"'"!==t.charAt(e)||n("'")?i+=t.charAt(e):s=!1;else switch(t.charAt(e)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":n("'")?i+="'":s=!0;break;default:i+=t.charAt(e)}return i},_get:function(t,e){return void 0!==t.settings[e]?t.settings[e]:this._defaults[e]},_setDateFromField:function(t,e){if(t.input.val()!==t.lastVal){var i=this._get(t,"dateFormat"),s=t.lastVal=t.input?t.input.val():null,n=this._getDefaultDate(t),o=n,a=this._getFormatConfig(t);try{o=this.parseDate(i,s,a)||n}catch(r){s=e?"":s}t.selectedDay=o.getDate(),t.drawMonth=t.selectedMonth=o.getMonth(),t.drawYear=t.selectedYear=o.getFullYear(),t.currentDay=s?o.getDate():0,t.currentMonth=s?o.getMonth():0,t.currentYear=s?o.getFullYear():0,this._adjustInstDate(t)}},_getDefaultDate:function(t){return this._restrictMinMax(t,this._determineDate(t,this._get(t,"defaultDate"),new Date))},_determineDate:function(e,i,s){var n=function(t){var e=new Date;return e.setDate(e.getDate()+t),e},o=function(i){try{return t.datepicker.parseDate(t.datepicker._get(e,"dateFormat"),i,t.datepicker._getFormatConfig(e))}catch(s){}for(var n=(i.toLowerCase().match(/^c/)?t.datepicker._getDate(e):null)||new Date,o=n.getFullYear(),a=n.getMonth(),r=n.getDate(),h=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,l=h.exec(i);l;){switch(l[2]||"d"){case"d":case"D":r+=parseInt(l[1],10);break;case"w":case"W":r+=7*parseInt(l[1],10);break;case"m":case"M":a+=parseInt(l[1],10),r=Math.min(r,t.datepicker._getDaysInMonth(o,a));break;case"y":case"Y":o+=parseInt(l[1],10),r=Math.min(r,t.datepicker._getDaysInMonth(o,a))}l=h.exec(i)}return new Date(o,a,r)},a=null==i||""===i?s:"string"==typeof i?o(i):"number"==typeof i?isNaN(i)?s:n(i):new Date(i.getTime());return a=a&&"Invalid Date"==""+a?s:a,a&&(a.setHours(0),a.setMinutes(0),a.setSeconds(0),a.setMilliseconds(0)),this._daylightSavingAdjust(a)},_daylightSavingAdjust:function(t){return t?(t.setHours(t.getHours()>12?t.getHours()+2:0),t):null},_setDate:function(t,e,i){var s=!e,n=t.selectedMonth,o=t.selectedYear,a=this._restrictMinMax(t,this._determineDate(t,e,new Date));t.selectedDay=t.currentDay=a.getDate(),t.drawMonth=t.selectedMonth=t.currentMonth=a.getMonth(),t.drawYear=t.selectedYear=t.currentYear=a.getFullYear(),n===t.selectedMonth&&o===t.selectedYear||i||this._notifyChange(t),this._adjustInstDate(t),t.input&&t.input.val(s?"":this._formatDate(t))},_getDate:function(t){var e=!t.currentYear||t.input&&""===t.input.val()?null:this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return e},_attachHandlers:function(e){var i=this._get(e,"stepMonths"),s="#"+e.id.replace(/\\\\/g,"\\");e.dpDiv.find("[data-handler]").map(function(){var e={prev:function(){t.datepicker._adjustDate(s,-i,"M")},next:function(){t.datepicker._adjustDate(s,+i,"M")},hide:function(){t.datepicker._hideDatepicker()},today:function(){t.datepicker._gotoToday(s)},selectDay:function(){return t.datepicker._selectDay(s,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return t.datepicker._selectMonthYear(s,this,"M"),!1},selectYear:function(){return t.datepicker._selectMonthYear(s,this,"Y"),!1}};t(this).on(this.getAttribute("data-event"),e[this.getAttribute("data-handler")])})},_generateHTML:function(t){var e,i,s,n,o,a,r,h,l,c,u,d,p,f,g,m,_,v,b,y,w,k,x,C,D,I,T,P,M,S,H,z,O,A,N,W,E,F,L,R=new Date,B=this._daylightSavingAdjust(new Date(R.getFullYear(),R.getMonth(),R.getDate())),Y=this._get(t,"isRTL"),j=this._get(t,"showButtonPanel"),q=this._get(t,"hideIfNoPrevNext"),K=this._get(t,"navigationAsDateFormat"),U=this._getNumberOfMonths(t),V=this._get(t,"showCurrentAtPos"),$=this._get(t,"stepMonths"),X=1!==U[0]||1!==U[1],G=this._daylightSavingAdjust(t.currentDay?new Date(t.currentYear,t.currentMonth,t.currentDay):new Date(9999,9,9)),Q=this._getMinMaxDate(t,"min"),J=this._getMinMaxDate(t,"max"),Z=t.drawMonth-V,te=t.drawYear;if(0>Z&&(Z+=12,te--),J)for(e=this._daylightSavingAdjust(new Date(J.getFullYear(),J.getMonth()-U[0]*U[1]+1,J.getDate())),e=Q&&Q>e?Q:e;this._daylightSavingAdjust(new Date(te,Z,1))>e;)Z--,0>Z&&(Z=11,te--);for(t.drawMonth=Z,t.drawYear=te,i=this._get(t,"prevText"),i=K?this.formatDate(i,this._daylightSavingAdjust(new Date(te,Z-$,1)),this._getFormatConfig(t)):i,s=this._canAdjustMonth(t,-1,te,Z)?"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click' title='"+i+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"e":"w")+"'>"+i+"</span></a>":q?"":"<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+i+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"e":"w")+"'>"+i+"</span></a>",n=this._get(t,"nextText"),n=K?this.formatDate(n,this._daylightSavingAdjust(new Date(te,Z+$,1)),this._getFormatConfig(t)):n,o=this._canAdjustMonth(t,1,te,Z)?"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click' title='"+n+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"w":"e")+"'>"+n+"</span></a>":q?"":"<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+n+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"w":"e")+"'>"+n+"</span></a>",a=this._get(t,"currentText"),r=this._get(t,"gotoCurrent")&&t.currentDay?G:B,a=K?this.formatDate(a,r,this._getFormatConfig(t)):a,h=t.inline?"":"<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>"+this._get(t,"closeText")+"</button>",l=j?"<div class='ui-datepicker-buttonpane ui-widget-content'>"+(Y?h:"")+(this._isInRange(t,r)?"<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'>"+a+"</button>":"")+(Y?"":h)+"</div>":"",c=parseInt(this._get(t,"firstDay"),10),c=isNaN(c)?0:c,u=this._get(t,"showWeek"),d=this._get(t,"dayNames"),p=this._get(t,"dayNamesMin"),f=this._get(t,"monthNames"),g=this._get(t,"monthNamesShort"),m=this._get(t,"beforeShowDay"),_=this._get(t,"showOtherMonths"),v=this._get(t,"selectOtherMonths"),b=this._getDefaultDate(t),y="",k=0;U[0]>k;k++){for(x="",this.maxRows=4,C=0;U[1]>C;C++){if(D=this._daylightSavingAdjust(new Date(te,Z,t.selectedDay)),I=" ui-corner-all",T="",X){if(T+="<div class='ui-datepicker-group",U[1]>1)switch(C){case 0:T+=" ui-datepicker-group-first",I=" ui-corner-"+(Y?"right":"left");break;case U[1]-1:T+=" ui-datepicker-group-last",I=" ui-corner-"+(Y?"left":"right");break;default:T+=" ui-datepicker-group-middle",I=""}T+="'>"}for(T+="<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix"+I+"'>"+(/all|left/.test(I)&&0===k?Y?o:s:"")+(/all|right/.test(I)&&0===k?Y?s:o:"")+this._generateMonthYearHeader(t,Z,te,Q,J,k>0||C>0,f,g)+"</div><table class='ui-datepicker-calendar'><thead>"+"<tr>",P=u?"<th class='ui-datepicker-week-col'>"+this._get(t,"weekHeader")+"</th>":"",w=0;7>w;w++)M=(w+c)%7,P+="<th scope='col'"+((w+c+6)%7>=5?" class='ui-datepicker-week-end'":"")+">"+"<span title='"+d[M]+"'>"+p[M]+"</span></th>";for(T+=P+"</tr></thead><tbody>",S=this._getDaysInMonth(te,Z),te===t.selectedYear&&Z===t.selectedMonth&&(t.selectedDay=Math.min(t.selectedDay,S)),H=(this._getFirstDayOfMonth(te,Z)-c+7)%7,z=Math.ceil((H+S)/7),O=X?this.maxRows>z?this.maxRows:z:z,this.maxRows=O,A=this._daylightSavingAdjust(new Date(te,Z,1-H)),N=0;O>N;N++){for(T+="<tr>",W=u?"<td class='ui-datepicker-week-col'>"+this._get(t,"calculateWeek")(A)+"</td>":"",w=0;7>w;w++)E=m?m.apply(t.input?t.input[0]:null,[A]):[!0,""],F=A.getMonth()!==Z,L=F&&!v||!E[0]||Q&&Q>A||J&&A>J,W+="<td class='"+((w+c+6)%7>=5?" ui-datepicker-week-end":"")+(F?" ui-datepicker-other-month":"")+(A.getTime()===D.getTime()&&Z===t.selectedMonth&&t._keyEvent||b.getTime()===A.getTime()&&b.getTime()===D.getTime()?" "+this._dayOverClass:"")+(L?" "+this._unselectableClass+" ui-state-disabled":"")+(F&&!_?"":" "+E[1]+(A.getTime()===G.getTime()?" "+this._currentClass:"")+(A.getTime()===B.getTime()?" ui-datepicker-today":""))+"'"+(F&&!_||!E[2]?"":" title='"+E[2].replace(/'/g,"'")+"'")+(L?"":" data-handler='selectDay' data-event='click' data-month='"+A.getMonth()+"' data-year='"+A.getFullYear()+"'")+">"+(F&&!_?" ":L?"<span class='ui-state-default'>"+A.getDate()+"</span>":"<a class='ui-state-default"+(A.getTime()===B.getTime()?" ui-state-highlight":"")+(A.getTime()===G.getTime()?" ui-state-active":"")+(F?" ui-priority-secondary":"")+"' href='#'>"+A.getDate()+"</a>")+"</td>",A.setDate(A.getDate()+1),A=this._daylightSavingAdjust(A);T+=W+"</tr>"}Z++,Z>11&&(Z=0,te++),T+="</tbody></table>"+(X?"</div>"+(U[0]>0&&C===U[1]-1?"<div class='ui-datepicker-row-break'></div>":""):""),x+=T}y+=x}return y+=l,t._keyEvent=!1,y},_generateMonthYearHeader:function(t,e,i,s,n,o,a,r){var h,l,c,u,d,p,f,g,m=this._get(t,"changeMonth"),_=this._get(t,"changeYear"),v=this._get(t,"showMonthAfterYear"),b="<div class='ui-datepicker-title'>",y="";if(o||!m)y+="<span class='ui-datepicker-month'>"+a[e]+"</span>";else{for(h=s&&s.getFullYear()===i,l=n&&n.getFullYear()===i,y+="<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>",c=0;12>c;c++)(!h||c>=s.getMonth())&&(!l||n.getMonth()>=c)&&(y+="<option value='"+c+"'"+(c===e?" selected='selected'":"")+">"+r[c]+"</option>");y+="</select>"}if(v||(b+=y+(!o&&m&&_?"":" ")),!t.yearshtml)if(t.yearshtml="",o||!_)b+="<span class='ui-datepicker-year'>"+i+"</span>";else{for(u=this._get(t,"yearRange").split(":"),d=(new Date).getFullYear(),p=function(t){var e=t.match(/c[+\-].*/)?i+parseInt(t.substring(1),10):t.match(/[+\-].*/)?d+parseInt(t,10):parseInt(t,10);return isNaN(e)?d:e},f=p(u[0]),g=Math.max(f,p(u[1]||"")),f=s?Math.max(f,s.getFullYear()):f,g=n?Math.min(g,n.getFullYear()):g,t.yearshtml+="<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";g>=f;f++)t.yearshtml+="<option value='"+f+"'"+(f===i?" selected='selected'":"")+">"+f+"</option>";t.yearshtml+="</select>",b+=t.yearshtml,t.yearshtml=null}return b+=this._get(t,"yearSuffix"),v&&(b+=(!o&&m&&_?"":" ")+y),b+="</div>"},_adjustInstDate:function(t,e,i){var s=t.selectedYear+("Y"===i?e:0),n=t.selectedMonth+("M"===i?e:0),o=Math.min(t.selectedDay,this._getDaysInMonth(s,n))+("D"===i?e:0),a=this._restrictMinMax(t,this._daylightSavingAdjust(new Date(s,n,o)));t.selectedDay=a.getDate(),t.drawMonth=t.selectedMonth=a.getMonth(),t.drawYear=t.selectedYear=a.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(t)},_restrictMinMax:function(t,e){var i=this._getMinMaxDate(t,"min"),s=this._getMinMaxDate(t,"max"),n=i&&i>e?i:e;return s&&n>s?s:n},_notifyChange:function(t){var e=this._get(t,"onChangeMonthYear");e&&e.apply(t.input?t.input[0]:null,[t.selectedYear,t.selectedMonth+1,t])},_getNumberOfMonths:function(t){var e=this._get(t,"numberOfMonths");return null==e?[1,1]:"number"==typeof e?[1,e]:e},_getMinMaxDate:function(t,e){return this._determineDate(t,this._get(t,e+"Date"),null)},_getDaysInMonth:function(t,e){return 32-this._daylightSavingAdjust(new Date(t,e,32)).getDate()},_getFirstDayOfMonth:function(t,e){return new Date(t,e,1).getDay()},_canAdjustMonth:function(t,e,i,s){var n=this._getNumberOfMonths(t),o=this._daylightSavingAdjust(new Date(i,s+(0>e?e:n[0]*n[1]),1));return 0>e&&o.setDate(this._getDaysInMonth(o.getFullYear(),o.getMonth())),this._isInRange(t,o)},_isInRange:function(t,e){var i,s,n=this._getMinMaxDate(t,"min"),o=this._getMinMaxDate(t,"max"),a=null,r=null,h=this._get(t,"yearRange");return h&&(i=h.split(":"),s=(new Date).getFullYear(),a=parseInt(i[0],10),r=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(a+=s),i[1].match(/[+\-].*/)&&(r+=s)),(!n||e.getTime()>=n.getTime())&&(!o||e.getTime()<=o.getTime())&&(!a||e.getFullYear()>=a)&&(!r||r>=e.getFullYear())},_getFormatConfig:function(t){var e=this._get(t,"shortYearCutoff");return e="string"!=typeof e?e:(new Date).getFullYear()%100+parseInt(e,10),{shortYearCutoff:e,dayNamesShort:this._get(t,"dayNamesShort"),dayNames:this._get(t,"dayNames"),monthNamesShort:this._get(t,"monthNamesShort"),monthNames:this._get(t,"monthNames")}},_formatDate:function(t,e,i,s){e||(t.currentDay=t.selectedDay,t.currentMonth=t.selectedMonth,t.currentYear=t.selectedYear);var n=e?"object"==typeof e?e:this._daylightSavingAdjust(new Date(s,i,e)):this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return this.formatDate(this._get(t,"dateFormat"),n,this._getFormatConfig(t))}}),t.fn.datepicker=function(e){if(!this.length)return this;t.datepicker.initialized||(t(document).on("mousedown",t.datepicker._checkExternalClick),t.datepicker.initialized=!0),0===t("#"+t.datepicker._mainDivId).length&&t("body").append(t.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof e||"isDisabled"!==e&&"getDate"!==e&&"widget"!==e?"option"===e&&2===arguments.length&&"string"==typeof arguments[1]?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof e?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this].concat(i)):t.datepicker._attachDatepicker(this,e)}):t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i))},t.datepicker=new s,t.datepicker.initialized=!1,t.datepicker.uuid=(new Date).getTime(),t.datepicker.version="1.12.1",t.datepicker,t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var _=!1;t(document).on("mouseup",function(){_=!1}),t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!_){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,s=1===e.which,n="string"==typeof this.options.cancel&&e.target.nodeName?t(e.target).closest(this.options.cancel).length:!1;return s&&!n&&this._mouseCapture(e)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(e)!==!1,!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),_=!0,!0)):!0}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,_=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.ui.plugin={add:function(e,i,s){var n,o=t.ui[e].prototype;for(n in s)o.plugins[n]=o.plugins[n]||[],o.plugins[n].push([i,s[n]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;o.length>n;n++)t.options[o[n][0]]&&o[n][1].apply(t.element,i)}},t.ui.safeBlur=function(e){e&&"body"!==e.nodeName.toLowerCase()&&t(e).trigger("blur")},t.widget("ui.draggable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this._addClass("ui-draggable"),this._setHandleClassName(),this._mouseInit()},_setOption:function(t,e){this._super(t,e),"handle"===t&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){return(this.helper||this.element).is(".ui-draggable-dragging")?(this.destroyOnClear=!0,void 0):(this._removeHandleClassName(),this._mouseDestroy(),void 0)},_mouseCapture:function(e){var i=this.options;return this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(e),this.handle?(this._blurActiveElement(e),this._blockFrames(i.iframeFix===!0?"iframe":i.iframeFix),!0):!1)},_blockFrames:function(e){this.iframeBlocks=this.document.find(e).map(function(){var e=t(this);return t("<div>").css("position","absolute").appendTo(e.parent()).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(e){var i=t.ui.safeActiveElement(this.document[0]),s=t(e.target);s.closest(i).length||t.ui.safeBlur(i)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===t(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(e),this.originalPosition=this.position=this._generatePosition(e,!1),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",e)===!1?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_refreshOffsets:function(t){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:t.pageX-this.offset.left,top:t.pageY-this.offset.top}},_mouseDrag:function(e,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",e,s)===!1)return this._mouseUp(new t.Event("mouseup",e)),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",e)!==!1&&i._clear()}):this._trigger("stop",e)!==!1&&this._clear(),!1},_mouseUp:function(e){return this._unblockFrames(),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),this.handleElement.is(e.target)&&this.element.trigger("focus"),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp(new t.Event("mouseup",{target:this.element[0]})):this._clear(),this},_getHandle:function(e){return this.options.handle?!!t(e.target).closest(this.element.find(this.options.handle)).length:!0},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this._addClass(this.handleElement,"ui-draggable-handle")},_removeHandleClassName:function(){this._removeClass(this.handleElement,"ui-draggable-handle")},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper),n=s?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return n.parents("body").length||n.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&n[0]===this.element[0]&&this._setPositionRelative(),n[0]===this.element[0]||/(fixed|absolute)/.test(n.css("position"))||n.css("position","absolute"),n},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_isRootNode:function(t){return/(html|body)/i.test(t.tagName)||t===this.document[0]},_getParentOffset:function(){var e=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var t=this.element.position(),e=this._isRootNode(this.scrollParent[0]);return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+(e?0:this.scrollParent.scrollTop()),left:t.left-(parseInt(this.helper.css("left"),10)||0)+(e?0:this.scrollParent.scrollLeft())} + },_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options,o=this.document[0];return this.relativeContainer=null,n.containment?"window"===n.containment?(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):"document"===n.containment?(this.containment=[0,0,t(o).width()-this.helperProportions.width-this.margins.left,(t(o).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):n.containment.constructor===Array?(this.containment=n.containment,void 0):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=t(n.containment),s=i[0],s&&(e=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i),void 0):(this.containment=null,void 0)},_convertPositionTo:function(t,e){e||(e=this.position);var i="absolute"===t?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:e.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:e.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(t,e){var i,s,n,o,a=this.options,r=this._isRootNode(this.scrollParent[0]),h=t.pageX,l=t.pageY;return r&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),e&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,t.pageX-this.offset.click.left<i[0]&&(h=i[0]+this.offset.click.left),t.pageY-this.offset.click.top<i[1]&&(l=i[1]+this.offset.click.top),t.pageX-this.offset.click.left>i[2]&&(h=i[2]+this.offset.click.left),t.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),a.grid&&(n=a.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/a.grid[1])*a.grid[1]:this.originalPageY,l=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-a.grid[1]:n+a.grid[1]:n,o=a.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/a.grid[0])*a.grid[0]:this.originalPageX,h=i?o-this.offset.click.left>=i[0]||o-this.offset.click.left>i[2]?o:o-this.offset.click.left>=i[0]?o-a.grid[0]:o+a.grid[0]:o),"y"===a.axis&&(h=this.originalPageX),"x"===a.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:r?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:r?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s,this],!0),/^(drag|start|stop)/.test(e)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i,s){var n=t.extend({},i,{item:s.element});s.sortables=[],t(s.options.connectToSortable).each(function(){var i=t(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",e,n))})},stop:function(e,i,s){var n=t.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,t.each(s.sortables,function(){var t=this;t.isOver?(t.isOver=0,s.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,n))})},drag:function(e,i,s){t.each(s.sortables,function(){var n=!1,o=this;o.positionAbs=s.positionAbs,o.helperProportions=s.helperProportions,o.offset.click=s.offset.click,o._intersectsWith(o.containerCache)&&(n=!0,t.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==o&&this._intersectsWith(this.containerCache)&&t.contains(o.element[0],this.element[0])&&(n=!1),n})),n?(o.isOver||(o.isOver=1,s._parent=i.helper.parent(),o.currentItem=i.helper.appendTo(o.element).data("ui-sortable-item",!0),o.options._helper=o.options.helper,o.options.helper=function(){return i.helper[0]},e.target=o.currentItem[0],o._mouseCapture(e,!0),o._mouseStart(e,!0,!0),o.offset.click.top=s.offset.click.top,o.offset.click.left=s.offset.click.left,o.offset.parent.left-=s.offset.parent.left-o.offset.parent.left,o.offset.parent.top-=s.offset.parent.top-o.offset.parent.top,s._trigger("toSortable",e),s.dropped=o.element,t.each(s.sortables,function(){this.refreshPositions()}),s.currentItem=s.element,o.fromOutside=s),o.currentItem&&(o._mouseDrag(e),i.position=o.position)):o.isOver&&(o.isOver=0,o.cancelHelperRemoval=!0,o.options._revert=o.options.revert,o.options.revert=!1,o._trigger("out",e,o._uiHash(o)),o._mouseStop(e,!0),o.options.revert=o.options._revert,o.options.helper=o.options._helper,o.placeholder&&o.placeholder.remove(),i.helper.appendTo(s._parent),s._refreshOffsets(e),i.position=s._generatePosition(e,!0),s._trigger("fromSortable",e),s.dropped=!1,t.each(s.sortables,function(){this.refreshPositions()}))})}}),t.ui.plugin.add("draggable","cursor",{start:function(e,i,s){var n=t("body"),o=s.options;n.css("cursor")&&(o._cursor=n.css("cursor")),n.css("cursor",o.cursor)},stop:function(e,i,s){var n=s.options;n._cursor&&t("body").css("cursor",n._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i,s){var n=t(i.helper),o=s.options;n.css("opacity")&&(o._opacity=n.css("opacity")),n.css("opacity",o.opacity)},stop:function(e,i,s){var n=s.options;n._opacity&&t(i.helper).css("opacity",n._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(t,e,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(e,i,s){var n=s.options,o=!1,a=s.scrollParentNotHidden[0],r=s.document[0];a!==r&&"HTML"!==a.tagName?(n.axis&&"x"===n.axis||(s.overflowOffset.top+a.offsetHeight-e.pageY<n.scrollSensitivity?a.scrollTop=o=a.scrollTop+n.scrollSpeed:e.pageY-s.overflowOffset.top<n.scrollSensitivity&&(a.scrollTop=o=a.scrollTop-n.scrollSpeed)),n.axis&&"y"===n.axis||(s.overflowOffset.left+a.offsetWidth-e.pageX<n.scrollSensitivity?a.scrollLeft=o=a.scrollLeft+n.scrollSpeed:e.pageX-s.overflowOffset.left<n.scrollSensitivity&&(a.scrollLeft=o=a.scrollLeft-n.scrollSpeed))):(n.axis&&"x"===n.axis||(e.pageY-t(r).scrollTop()<n.scrollSensitivity?o=t(r).scrollTop(t(r).scrollTop()-n.scrollSpeed):t(window).height()-(e.pageY-t(r).scrollTop())<n.scrollSensitivity&&(o=t(r).scrollTop(t(r).scrollTop()+n.scrollSpeed))),n.axis&&"y"===n.axis||(e.pageX-t(r).scrollLeft()<n.scrollSensitivity?o=t(r).scrollLeft(t(r).scrollLeft()-n.scrollSpeed):t(window).width()-(e.pageX-t(r).scrollLeft())<n.scrollSensitivity&&(o=t(r).scrollLeft(t(r).scrollLeft()+n.scrollSpeed)))),o!==!1&&t.ui.ddmanager&&!n.dropBehaviour&&t.ui.ddmanager.prepareOffsets(s,e)}}),t.ui.plugin.add("draggable","snap",{start:function(e,i,s){var n=s.options;s.snapElements=[],t(n.snap.constructor!==String?n.snap.items||":data(ui-draggable)":n.snap).each(function(){var e=t(this),i=e.offset();this!==s.element[0]&&s.snapElements.push({item:this,width:e.outerWidth(),height:e.outerHeight(),top:i.top,left:i.left})})},drag:function(e,i,s){var n,o,a,r,h,l,c,u,d,p,f=s.options,g=f.snapTolerance,m=i.offset.left,_=m+s.helperProportions.width,v=i.offset.top,b=v+s.helperProportions.height;for(d=s.snapElements.length-1;d>=0;d--)h=s.snapElements[d].left-s.margins.left,l=h+s.snapElements[d].width,c=s.snapElements[d].top-s.margins.top,u=c+s.snapElements[d].height,h-g>_||m>l+g||c-g>b||v>u+g||!t.contains(s.snapElements[d].item.ownerDocument,s.snapElements[d].item)?(s.snapElements[d].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[d].item})),s.snapElements[d].snapping=!1):("inner"!==f.snapMode&&(n=g>=Math.abs(c-b),o=g>=Math.abs(u-v),a=g>=Math.abs(h-_),r=g>=Math.abs(l-m),n&&(i.position.top=s._convertPositionTo("relative",{top:c-s.helperProportions.height,left:0}).top),o&&(i.position.top=s._convertPositionTo("relative",{top:u,left:0}).top),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left)),p=n||o||a||r,"outer"!==f.snapMode&&(n=g>=Math.abs(c-v),o=g>=Math.abs(u-b),a=g>=Math.abs(h-m),r=g>=Math.abs(l-_),n&&(i.position.top=s._convertPositionTo("relative",{top:c,left:0}).top),o&&(i.position.top=s._convertPositionTo("relative",{top:u-s.helperProportions.height,left:0}).top),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left)),!s.snapElements[d].snapping&&(n||o||a||r||p)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[d].item})),s.snapElements[d].snapping=n||o||a||r||p)}}),t.ui.plugin.add("draggable","stack",{start:function(e,i,s){var n,o=s.options,a=t.makeArray(t(o.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});a.length&&(n=parseInt(t(a[0]).css("zIndex"),10)||0,t(a).each(function(e){t(this).css("zIndex",n+e)}),this.css("zIndex",n+a.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i,s){var n=t(i.helper),o=s.options;n.css("zIndex")&&(o._zIndex=n.css("zIndex")),n.css("zIndex",o.zIndex)},stop:function(e,i,s){var n=s.options;n._zIndex&&t(i.helper).css("zIndex",n._zIndex)}}),t.ui.draggable,t.widget("ui.resizable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,classes:{"ui-resizable-se":"ui-icon ui-icon-gripsmall-diagonal-se"},containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(t){return parseFloat(t)||0},_isNumber:function(t){return!isNaN(parseFloat(t))},_hasScroll:function(e,i){if("hidden"===t(e).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return e[s]>0?!0:(e[s]=1,n=e[s]>0,e[s]=0,n)},_create:function(){var e,i=this.options,s=this;this._addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!i.aspectRatio,aspectRatio:i.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:i.helper||i.ghost||i.animate?i.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(t("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,e={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(e),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(e),this._proportionallyResize()),this._setupHandles(),i.autoHide&&t(this.element).on("mouseenter",function(){i.disabled||(s._removeClass("ui-resizable-autohide"),s._handles.show())}).on("mouseleave",function(){i.disabled||s.resizing||(s._addClass("ui-resizable-autohide"),s._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeData("resizable").removeData("ui-resizable").off(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;default:}},_setupHandles:function(){var e,i,s,n,o,a=this.options,r=this;if(this.handles=a.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=t(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),s=this.handles.split(","),this.handles={},i=0;s.length>i;i++)e=t.trim(s[i]),n="ui-resizable-"+e,o=t("<div>"),this._addClass(o,"ui-resizable-handle "+n),o.css({zIndex:a.zIndex}),this.handles[e]=".ui-resizable-"+e,this.element.append(o);this._renderAxis=function(e){var i,s,n,o;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=t(this.handles[i]),this._on(this.handles[i],{mousedown:r._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=t(this.handles[i],this.element),o=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,o),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){r.resizing||(this.className&&(o=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),r.axis=o&&o[1]?o[1]:"se")}),a.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._handles.remove()},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(e){var i,s,n,o=this.options,a=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),o.containment&&(i+=t(o.containment).scrollLeft()||0,s+=t(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:a.width(),height:a.height()},this.originalSize=this._helper?{width:a.outerWidth(),height:a.outerHeight()}:{width:a.width(),height:a.height()},this.sizeDiff={width:a.outerWidth()-a.width(),height:a.outerHeight()-a.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:e.pageX,top:e.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===n?this.axis+"-resize":n),this._addClass("ui-resizable-resizing"),this._propagate("start",e),!0},_mouseDrag:function(e){var i,s,n=this.originalMousePosition,o=this.axis,a=e.pageX-n.left||0,r=e.pageY-n.top||0,h=this._change[o];return this._updatePrevProperties(),h?(i=h.apply(this,[e,a,r]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",e,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,o,a,r,h,l=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:c.sizeDiff.height,o=s?0:c.sizeDiff.width,a={width:c.helper.width()-o,height:c.helper.height()-n},r=parseFloat(c.element.css("left"))+(c.position.left-c.originalPosition.left)||null,h=parseFloat(c.element.css("top"))+(c.position.top-c.originalPosition.top)||null,l.animate||this.element.css(t.extend(a,{top:h,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!l.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s,n,o,a=this.options;o={minWidth:this._isNumber(a.minWidth)?a.minWidth:0,maxWidth:this._isNumber(a.maxWidth)?a.maxWidth:1/0,minHeight:this._isNumber(a.minHeight)?a.minHeight:0,maxHeight:this._isNumber(a.maxHeight)?a.maxHeight:1/0},(this._aspectRatio||t)&&(e=o.minHeight*this.aspectRatio,s=o.minWidth/this.aspectRatio,i=o.maxHeight*this.aspectRatio,n=o.maxWidth/this.aspectRatio,e>o.minWidth&&(o.minWidth=e),s>o.minHeight&&(o.minHeight=s),o.maxWidth>i&&(o.maxWidth=i),o.maxHeight>n&&(o.maxHeight=n)),this._vBoundaries=o},_updateCache:function(t){this.offset=this.helper.offset(),this._isNumber(t.left)&&(this.position.left=t.left),this._isNumber(t.top)&&(this.position.top=t.top),this._isNumber(t.height)&&(this.size.height=t.height),this._isNumber(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,i=this.size,s=this.axis;return this._isNumber(t.height)?t.width=t.height*this.aspectRatio:this._isNumber(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===s&&(t.left=e.left+(i.width-t.width),t.top=null),"nw"===s&&(t.top=e.top+(i.height-t.height),t.left=e.left+(i.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,i=this.axis,s=this._isNumber(t.width)&&e.maxWidth&&e.maxWidth<t.width,n=this._isNumber(t.height)&&e.maxHeight&&e.maxHeight<t.height,o=this._isNumber(t.width)&&e.minWidth&&e.minWidth>t.width,a=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,r=this.originalPosition.left+this.originalSize.width,h=this.originalPosition.top+this.originalSize.height,l=/sw|nw|w/.test(i),c=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),a&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&l&&(t.left=r-e.minWidth),s&&l&&(t.left=r-e.maxWidth),a&&c&&(t.top=h-e.minHeight),n&&c&&(t.top=h-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];4>e;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;this._proportionallyResizeElements.length>e;e++)t=this._proportionallyResizeElements[e],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(t)),t.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("<div style='overflow:hidden;'></div>"),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,o=n.length&&/textarea/i.test(n[0].nodeName),a=o&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=o?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-a},l=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,c=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(h,c&&l?{top:c,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var e,i,s,n,o,a,r,h=t(this).resizable("instance"),l=h.options,c=h.element,u=l.containment,d=u instanceof t?u.get(0):/parent/.test(u)?c.parent().get(0):u;d&&(h.containerElement=t(d),/document/.test(u)||u===document?(h.containerOffset={left:0,top:0},h.containerPosition={left:0,top:0},h.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(e=t(d),i=[],t(["Top","Right","Left","Bottom"]).each(function(t,s){i[t]=h._num(e.css("padding"+s))}),h.containerOffset=e.offset(),h.containerPosition=e.position(),h.containerSize={height:e.innerHeight()-i[3],width:e.innerWidth()-i[1]},s=h.containerOffset,n=h.containerSize.height,o=h.containerSize.width,a=h._hasScroll(d,"left")?d.scrollWidth:o,r=h._hasScroll(d)?d.scrollHeight:n,h.parentData={element:d,left:s.left,top:s.top,width:a,height:r}))},resize:function(e){var i,s,n,o,a=t(this).resizable("instance"),r=a.options,h=a.containerOffset,l=a.position,c=a._aspectRatio||e.shiftKey,u={top:0,left:0},d=a.containerElement,p=!0;d[0]!==document&&/static/.test(d.css("position"))&&(u=h),l.left<(a._helper?h.left:0)&&(a.size.width=a.size.width+(a._helper?a.position.left-h.left:a.position.left-u.left),c&&(a.size.height=a.size.width/a.aspectRatio,p=!1),a.position.left=r.helper?h.left:0),l.top<(a._helper?h.top:0)&&(a.size.height=a.size.height+(a._helper?a.position.top-h.top:a.position.top),c&&(a.size.width=a.size.height*a.aspectRatio,p=!1),a.position.top=a._helper?h.top:0),n=a.containerElement.get(0)===a.element.parent().get(0),o=/relative|absolute/.test(a.containerElement.css("position")),n&&o?(a.offset.left=a.parentData.left+a.position.left,a.offset.top=a.parentData.top+a.position.top):(a.offset.left=a.element.offset().left,a.offset.top=a.element.offset().top),i=Math.abs(a.sizeDiff.width+(a._helper?a.offset.left-u.left:a.offset.left-h.left)),s=Math.abs(a.sizeDiff.height+(a._helper?a.offset.top-u.top:a.offset.top-h.top)),i+a.size.width>=a.parentData.width&&(a.size.width=a.parentData.width-i,c&&(a.size.height=a.size.width/a.aspectRatio,p=!1)),s+a.size.height>=a.parentData.height&&(a.size.height=a.parentData.height-s,c&&(a.size.width=a.size.height*a.aspectRatio,p=!1)),p||(a.position.left=a.prevPosition.left,a.position.top=a.prevPosition.top,a.size.width=a.prevSize.width,a.size.height=a.prevSize.height)},stop:function(){var e=t(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.containerPosition,o=e.containerElement,a=t(e.helper),r=a.offset(),h=a.outerWidth()-e.sizeDiff.width,l=a.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l}),e._helper&&!i.animate&&/static/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).resizable("instance"),i=e.options;t(i.alsoResize).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseFloat(e.width()),height:parseFloat(e.height()),left:parseFloat(e.css("left")),top:parseFloat(e.css("top"))})})},resize:function(e,i){var s=t(this).resizable("instance"),n=s.options,o=s.originalSize,a=s.originalPosition,r={height:s.size.height-o.height||0,width:s.size.width-o.width||0,top:s.position.top-a.top||0,left:s.position.left-a.left||0};t(n.alsoResize).each(function(){var e=t(this),s=t(this).data("ui-resizable-alsoresize"),n={},o=e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(o,function(t,e){var i=(s[e]||0)+(r[e]||0);i&&i>=0&&(n[e]=i||null)}),e.css(n)})},stop:function(){t(this).removeData("ui-resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).resizable("instance"),i=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:i.height,width:i.width,margin:0,left:0,top:0}),e._addClass(e.ghost,"ui-resizable-ghost"),t.uiBackCompat!==!1&&"string"==typeof e.options.ghost&&e.ghost.addClass(this.options.ghost),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).resizable("instance");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).resizable("instance");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e,i=t(this).resizable("instance"),s=i.options,n=i.size,o=i.originalSize,a=i.originalPosition,r=i.axis,h="number"==typeof s.grid?[s.grid,s.grid]:s.grid,l=h[0]||1,c=h[1]||1,u=Math.round((n.width-o.width)/l)*l,d=Math.round((n.height-o.height)/c)*c,p=o.width+u,f=o.height+d,g=s.maxWidth&&p>s.maxWidth,m=s.maxHeight&&f>s.maxHeight,_=s.minWidth&&s.minWidth>p,v=s.minHeight&&s.minHeight>f;s.grid=h,_&&(p+=l),v&&(f+=c),g&&(p-=l),m&&(f-=c),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=a.top-d):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=a.left-u):((0>=f-c||0>=p-l)&&(e=i._getPaddingPlusBorderDimensions(this)),f-c>0?(i.size.height=f,i.position.top=a.top-d):(f=c-e.height,i.size.height=f,i.position.top=a.top+o.height-f),p-l>0?(i.size.width=p,i.position.left=a.left-u):(p=l-e.width,i.size.width=p,i.position.left=a.left+o.width-p))}}),t.ui.resizable,t.widget("ui.dialog",{version:"1.12.1",options:{appendTo:"body",autoOpen:!0,buttons:[],classes:{"ui-dialog":"ui-corner-all","ui-dialog-titlebar":"ui-corner-all"},closeOnEscape:!0,closeText:"Close",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(e){var i=t(this).css(e).offset().top;0>i&&t(this).css("top",e.top-i)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},sizeRelatedOptions:{buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},resizableRelatedOptions:{maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),null==this.options.title&&null!=this.originalTitle&&(this.options.title=this.originalTitle),this.options.disabled&&(this.options.disabled=!1),this._createWrapper(),this.element.show().removeAttr("title").appendTo(this.uiDialog),this._addClass("ui-dialog-content","ui-widget-content"),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&t.fn.draggable&&this._makeDraggable(),this.options.resizable&&t.fn.resizable&&this._makeResizable(),this._isOpen=!1,this._trackFocus()},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var e=this.options.appendTo;return e&&(e.jquery||e.nodeType)?t(e):this.document.find(e||"body").eq(0)},_destroy:function(){var t,e=this.originalPosition;this._untrackInstance(),this._destroyOverlay(),this.element.removeUniqueId().css(this.originalCss).detach(),this.uiDialog.remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),t=e.parent.children().eq(e.index),t.length&&t[0]!==this.element[0]?t.before(this.element):e.parent.append(this.element)},widget:function(){return this.uiDialog + },disable:t.noop,enable:t.noop,close:function(e){var i=this;this._isOpen&&this._trigger("beforeClose",e)!==!1&&(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),this.opener.filter(":focusable").trigger("focus").length||t.ui.safeBlur(t.ui.safeActiveElement(this.document[0])),this._hide(this.uiDialog,this.options.hide,function(){i._trigger("close",e)}))},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(e,i){var s=!1,n=this.uiDialog.siblings(".ui-front:visible").map(function(){return+t(this).css("z-index")}).get(),o=Math.max.apply(null,n);return o>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",o+1),s=!0),s&&!i&&this._trigger("focus",e),s},open:function(){var e=this;return this._isOpen?(this._moveToTop()&&this._focusTabbable(),void 0):(this._isOpen=!0,this.opener=t(t.ui.safeActiveElement(this.document[0])),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){e._focusTabbable(),e._trigger("focus")}),this._makeFocusTarget(),this._trigger("open"),void 0)},_focusTabbable:function(){var t=this._focusedElement;t||(t=this.element.find("[autofocus]")),t.length||(t=this.element.find(":tabbable")),t.length||(t=this.uiDialogButtonPane.find(":tabbable")),t.length||(t=this.uiDialogTitlebarClose.filter(":tabbable")),t.length||(t=this.uiDialog),t.eq(0).trigger("focus")},_keepFocus:function(e){function i(){var e=t.ui.safeActiveElement(this.document[0]),i=this.uiDialog[0]===e||t.contains(this.uiDialog[0],e);i||this._focusTabbable()}e.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=t("<div>").hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._addClass(this.uiDialog,"ui-dialog","ui-widget ui-widget-content ui-front"),this._on(this.uiDialog,{keydown:function(e){if(this.options.closeOnEscape&&!e.isDefaultPrevented()&&e.keyCode&&e.keyCode===t.ui.keyCode.ESCAPE)return e.preventDefault(),this.close(e),void 0;if(e.keyCode===t.ui.keyCode.TAB&&!e.isDefaultPrevented()){var i=this.uiDialog.find(":tabbable"),s=i.filter(":first"),n=i.filter(":last");e.target!==n[0]&&e.target!==this.uiDialog[0]||e.shiftKey?e.target!==s[0]&&e.target!==this.uiDialog[0]||!e.shiftKey||(this._delay(function(){n.trigger("focus")}),e.preventDefault()):(this._delay(function(){s.trigger("focus")}),e.preventDefault())}},mousedown:function(t){this._moveToTop(t)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var e;this.uiDialogTitlebar=t("<div>"),this._addClass(this.uiDialogTitlebar,"ui-dialog-titlebar","ui-widget-header ui-helper-clearfix"),this._on(this.uiDialogTitlebar,{mousedown:function(e){t(e.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.trigger("focus")}}),this.uiDialogTitlebarClose=t("<button type='button'></button>").button({label:t("<a>").text(this.options.closeText).html(),icon:"ui-icon-closethick",showLabel:!1}).appendTo(this.uiDialogTitlebar),this._addClass(this.uiDialogTitlebarClose,"ui-dialog-titlebar-close"),this._on(this.uiDialogTitlebarClose,{click:function(t){t.preventDefault(),this.close(t)}}),e=t("<span>").uniqueId().prependTo(this.uiDialogTitlebar),this._addClass(e,"ui-dialog-title"),this._title(e),this.uiDialogTitlebar.prependTo(this.uiDialog),this.uiDialog.attr({"aria-labelledby":e.attr("id")})},_title:function(t){this.options.title?t.text(this.options.title):t.html(" ")},_createButtonPane:function(){this.uiDialogButtonPane=t("<div>"),this._addClass(this.uiDialogButtonPane,"ui-dialog-buttonpane","ui-widget-content ui-helper-clearfix"),this.uiButtonSet=t("<div>").appendTo(this.uiDialogButtonPane),this._addClass(this.uiButtonSet,"ui-dialog-buttonset"),this._createButtons()},_createButtons:function(){var e=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),t.isEmptyObject(i)||t.isArray(i)&&!i.length?(this._removeClass(this.uiDialog,"ui-dialog-buttons"),void 0):(t.each(i,function(i,s){var n,o;s=t.isFunction(s)?{click:s,text:i}:s,s=t.extend({type:"button"},s),n=s.click,o={icon:s.icon,iconPosition:s.iconPosition,showLabel:s.showLabel,icons:s.icons,text:s.text},delete s.click,delete s.icon,delete s.iconPosition,delete s.showLabel,delete s.icons,"boolean"==typeof s.text&&delete s.text,t("<button></button>",s).button(o).appendTo(e.uiButtonSet).on("click",function(){n.apply(e.element[0],arguments)})}),this._addClass(this.uiDialog,"ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),void 0)},_makeDraggable:function(){function e(t){return{position:t.position,offset:t.offset}}var i=this,s=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(s,n){i._addClass(t(this),"ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",s,e(n))},drag:function(t,s){i._trigger("drag",t,e(s))},stop:function(n,o){var a=o.offset.left-i.document.scrollLeft(),r=o.offset.top-i.document.scrollTop();s.position={my:"left top",at:"left"+(a>=0?"+":"")+a+" "+"top"+(r>=0?"+":"")+r,of:i.window},i._removeClass(t(this),"ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",n,e(o))}})},_makeResizable:function(){function e(t){return{originalPosition:t.originalPosition,originalSize:t.originalSize,position:t.position,size:t.size}}var i=this,s=this.options,n=s.resizable,o=this.uiDialog.css("position"),a="string"==typeof n?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:s.maxWidth,maxHeight:s.maxHeight,minWidth:s.minWidth,minHeight:this._minHeight(),handles:a,start:function(s,n){i._addClass(t(this),"ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",s,e(n))},resize:function(t,s){i._trigger("resize",t,e(s))},stop:function(n,o){var a=i.uiDialog.offset(),r=a.left-i.document.scrollLeft(),h=a.top-i.document.scrollTop();s.height=i.uiDialog.height(),s.width=i.uiDialog.width(),s.position={my:"left top",at:"left"+(r>=0?"+":"")+r+" "+"top"+(h>=0?"+":"")+h,of:i.window},i._removeClass(t(this),"ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",n,e(o))}}).css("position",o)},_trackFocus:function(){this._on(this.widget(),{focusin:function(e){this._makeFocusTarget(),this._focusedElement=t(e.target)}})},_makeFocusTarget:function(){this._untrackInstance(),this._trackingInstances().unshift(this)},_untrackInstance:function(){var e=this._trackingInstances(),i=t.inArray(this,e);-1!==i&&e.splice(i,1)},_trackingInstances:function(){var t=this.document.data("ui-dialog-instances");return t||(t=[],this.document.data("ui-dialog-instances",t)),t},_minHeight:function(){var t=this.options;return"auto"===t.height?t.minHeight:Math.min(t.minHeight,t.height)},_position:function(){var t=this.uiDialog.is(":visible");t||this.uiDialog.show(),this.uiDialog.position(this.options.position),t||this.uiDialog.hide()},_setOptions:function(e){var i=this,s=!1,n={};t.each(e,function(t,e){i._setOption(t,e),t in i.sizeRelatedOptions&&(s=!0),t in i.resizableRelatedOptions&&(n[t]=e)}),s&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",n)},_setOption:function(e,i){var s,n,o=this.uiDialog;"disabled"!==e&&(this._super(e,i),"appendTo"===e&&this.uiDialog.appendTo(this._appendTo()),"buttons"===e&&this._createButtons(),"closeText"===e&&this.uiDialogTitlebarClose.button({label:t("<a>").text(""+this.options.closeText).html()}),"draggable"===e&&(s=o.is(":data(ui-draggable)"),s&&!i&&o.draggable("destroy"),!s&&i&&this._makeDraggable()),"position"===e&&this._position(),"resizable"===e&&(n=o.is(":data(ui-resizable)"),n&&!i&&o.resizable("destroy"),n&&"string"==typeof i&&o.resizable("option","handles",i),n||i===!1||this._makeResizable()),"title"===e&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var t,e,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),t=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),e=Math.max(0,s.minHeight-t),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-t):"none","auto"===s.height?this.element.css({minHeight:e,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-t)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var e=t(this);return t("<div>").css({position:"absolute",width:e.outerWidth(),height:e.outerHeight()}).appendTo(e.parent()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(e){return t(e.target).closest(".ui-dialog").length?!0:!!t(e.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var e=!0;this._delay(function(){e=!1}),this.document.data("ui-dialog-overlays")||this._on(this.document,{focusin:function(t){e||this._allowInteraction(t)||(t.preventDefault(),this._trackingInstances()[0]._focusTabbable())}}),this.overlay=t("<div>").appendTo(this._appendTo()),this._addClass(this.overlay,null,"ui-widget-overlay ui-front"),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1)}},_destroyOverlay:function(){if(this.options.modal&&this.overlay){var t=this.document.data("ui-dialog-overlays")-1;t?this.document.data("ui-dialog-overlays",t):(this._off(this.document,"focusin"),this.document.removeData("ui-dialog-overlays")),this.overlay.remove(),this.overlay=null}}}),t.uiBackCompat!==!1&&t.widget("ui.dialog",t.ui.dialog,{options:{dialogClass:""},_createWrapper:function(){this._super(),this.uiDialog.addClass(this.options.dialogClass)},_setOption:function(t,e){"dialogClass"===t&&this.uiDialog.removeClass(this.options.dialogClass).addClass(e),this._superApply(arguments)}}),t.ui.dialog,t.widget("ui.droppable",{version:"1.12.1",widgetEventPrefix:"drop",options:{accept:"*",addClasses:!0,greedy:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(s)?s:function(t){return t.is(s)},this.proportions=function(){return arguments.length?(e=arguments[0],void 0):e?e:e={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},this._addToManager(i.scope),i.addClasses&&this._addClass("ui-droppable")},_addToManager:function(e){t.ui.ddmanager.droppables[e]=t.ui.ddmanager.droppables[e]||[],t.ui.ddmanager.droppables[e].push(this)},_splice:function(t){for(var e=0;t.length>e;e++)t[e]===this&&t.splice(e,1)},_destroy:function(){var e=t.ui.ddmanager.droppables[this.options.scope];this._splice(e)},_setOption:function(e,i){if("accept"===e)this.accept=t.isFunction(i)?i:function(t){return t.is(i)};else if("scope"===e){var s=t.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(e,i)},_activate:function(e){var i=t.ui.ddmanager.current;this._addActiveClass(),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this._removeActiveClass(),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._addHoverClass(),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._removeHoverClass(),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=t(this).droppable("instance");return i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&v(s,t.extend(i,{offset:i.element.offset()}),i.options.tolerance,e)?(n=!0,!1):void 0}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this._removeActiveClass(),this._removeHoverClass(),this._trigger("drop",e,this.ui(s)),this.element):!1):!1},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}},_addHoverClass:function(){this._addClass("ui-droppable-hover")},_removeHoverClass:function(){this._removeClass("ui-droppable-hover")},_addActiveClass:function(){this._addClass("ui-droppable-active")},_removeActiveClass:function(){this._removeClass("ui-droppable-active")}});var v=t.ui.intersect=function(){function t(t,e,i){return t>=e&&e+i>t}return function(e,i,s,n){if(!i.offset)return!1;var o=(e.positionAbs||e.position.absolute).left+e.margins.left,a=(e.positionAbs||e.position.absolute).top+e.margins.top,r=o+e.helperProportions.width,h=a+e.helperProportions.height,l=i.offset.left,c=i.offset.top,u=l+i.proportions().width,d=c+i.proportions().height;switch(s){case"fit":return o>=l&&u>=r&&a>=c&&d>=h;case"intersect":return o+e.helperProportions.width/2>l&&u>r-e.helperProportions.width/2&&a+e.helperProportions.height/2>c&&d>h-e.helperProportions.height/2;case"pointer":return t(n.pageY,c,i.proportions().height)&&t(n.pageX,l,i.proportions().width);case"touch":return(a>=c&&d>=a||h>=c&&d>=h||c>a&&h>d)&&(o>=l&&u>=o||r>=l&&u>=r||l>o&&r>u);default:return!1}}}();t.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,i){var s,n,o=t.ui.ddmanager.droppables[e.options.scope]||[],a=i?i.type:null,r=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;o.length>s;s++)if(!(o[s].options.disabled||e&&!o[s].accept.call(o[s].element[0],e.currentItem||e.element))){for(n=0;r.length>n;n++)if(r[n]===o[s].element[0]){o[s].proportions().height=0;continue t}o[s].visible="none"!==o[s].element.css("display"),o[s].visible&&("mousedown"===a&&o[s]._activate.call(o[s],i),o[s].offset=o[s].element.offset(),o[s].proportions({width:o[s].element[0].offsetWidth,height:o[s].element[0].offsetHeight}))}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&v(e,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").on("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,o,a=v(e,this,this.options.tolerance,i),r=!a&&this.isover?"isout":a&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,o=this.element.parents(":data(ui-droppable)").filter(function(){return t(this).droppable("instance").options.scope===n}),o.length&&(s=t(o[0]).droppable("instance"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").off("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}},t.uiBackCompat!==!1&&t.widget("ui.droppable",t.ui.droppable,{options:{hoverClass:!1,activeClass:!1},_addActiveClass:function(){this._super(),this.options.activeClass&&this.element.addClass(this.options.activeClass)},_removeActiveClass:function(){this._super(),this.options.activeClass&&this.element.removeClass(this.options.activeClass)},_addHoverClass:function(){this._super(),this.options.hoverClass&&this.element.addClass(this.options.hoverClass)},_removeHoverClass:function(){this._super(),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass)}}),t.ui.droppable,t.widget("ui.progressbar",{version:"1.12.1",options:{classes:{"ui-progressbar":"ui-corner-all","ui-progressbar-value":"ui-corner-left","ui-progressbar-complete":"ui-corner-right"},max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue(),this.element.attr({role:"progressbar","aria-valuemin":this.min}),this._addClass("ui-progressbar","ui-widget ui-widget-content"),this.valueDiv=t("<div>").appendTo(this.element),this._addClass(this.valueDiv,"ui-progressbar-value","ui-widget-header"),this._refreshValue()},_destroy:function(){this.element.removeAttr("role aria-valuemin aria-valuemax aria-valuenow"),this.valueDiv.remove()},value:function(t){return void 0===t?this.options.value:(this.options.value=this._constrainedValue(t),this._refreshValue(),void 0)},_constrainedValue:function(t){return void 0===t&&(t=this.options.value),this.indeterminate=t===!1,"number"!=typeof t&&(t=0),this.indeterminate?!1:Math.min(this.options.max,Math.max(this.min,t))},_setOptions:function(t){var e=t.value;delete t.value,this._super(t),this.options.value=this._constrainedValue(e),this._refreshValue()},_setOption:function(t,e){"max"===t&&(e=Math.max(this.min,e)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var e=this.options.value,i=this._percentage();this.valueDiv.toggle(this.indeterminate||e>this.min).width(i.toFixed(0)+"%"),this._toggleClass(this.valueDiv,"ui-progressbar-complete",null,e===this.options.max)._toggleClass("ui-progressbar-indeterminate",null,this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=t("<div>").appendTo(this.valueDiv),this._addClass(this.overlayDiv,"ui-progressbar-overlay"))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":e}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),e===this.options.max&&this._trigger("complete")}}),t.widget("ui.selectable",t.ui.mouse,{version:"1.12.1",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var e=this;this._addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){e.elementPos=t(e.element[0]).offset(),e.selectees=t(e.options.filter,e.element[0]),e._addClass(e.selectees,"ui-selectee"),e.selectees.each(function(){var i=t(this),s=i.offset(),n={left:s.left-e.elementPos.left,top:s.top-e.elementPos.top};t.data(this,"selectable-item",{element:this,$element:i,left:n.left,top:n.top,right:n.left+i.outerWidth(),bottom:n.top+i.outerHeight(),startselected:!1,selected:i.hasClass("ui-selected"),selecting:i.hasClass("ui-selecting"),unselecting:i.hasClass("ui-unselecting")})})},this.refresh(),this._mouseInit(),this.helper=t("<div>"),this._addClass(this.helper,"ui-selectable-helper")},_destroy:function(){this.selectees.removeData("selectable-item"),this._mouseDestroy()},_mouseStart:function(e){var i=this,s=this.options;this.opos=[e.pageX,e.pageY],this.elementPos=t(this.element[0]).offset(),this.options.disabled||(this.selectees=t(s.filter,this.element[0]),this._trigger("start",e),t(s.appendTo).append(this.helper),this.helper.css({left:e.pageX,top:e.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=t.data(this,"selectable-item");s.startselected=!0,e.metaKey||e.ctrlKey||(i._removeClass(s.$element,"ui-selected"),s.selected=!1,i._addClass(s.$element,"ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",e,{unselecting:s.element}))}),t(e.target).parents().addBack().each(function(){var s,n=t.data(this,"selectable-item");return n?(s=!e.metaKey&&!e.ctrlKey||!n.$element.hasClass("ui-selected"),i._removeClass(n.$element,s?"ui-unselecting":"ui-selected")._addClass(n.$element,s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",e,{selecting:n.element}):i._trigger("unselecting",e,{unselecting:n.element}),!1):void 0}))},_mouseDrag:function(e){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,o=this.opos[0],a=this.opos[1],r=e.pageX,h=e.pageY;return o>r&&(i=r,r=o,o=i),a>h&&(i=h,h=a,a=i),this.helper.css({left:o,top:a,width:r-o,height:h-a}),this.selectees.each(function(){var i=t.data(this,"selectable-item"),l=!1,c={};i&&i.element!==s.element[0]&&(c.left=i.left+s.elementPos.left,c.right=i.right+s.elementPos.left,c.top=i.top+s.elementPos.top,c.bottom=i.bottom+s.elementPos.top,"touch"===n.tolerance?l=!(c.left>r||o>c.right||c.top>h||a>c.bottom):"fit"===n.tolerance&&(l=c.left>o&&r>c.right&&c.top>a&&h>c.bottom),l?(i.selected&&(s._removeClass(i.$element,"ui-selected"),i.selected=!1),i.unselecting&&(s._removeClass(i.$element,"ui-unselecting"),i.unselecting=!1),i.selecting||(s._addClass(i.$element,"ui-selecting"),i.selecting=!0,s._trigger("selecting",e,{selecting:i.element}))):(i.selecting&&((e.metaKey||e.ctrlKey)&&i.startselected?(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,s._addClass(i.$element,"ui-selected"),i.selected=!0):(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,i.startselected&&(s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",e,{unselecting:i.element}))),i.selected&&(e.metaKey||e.ctrlKey||i.startselected||(s._removeClass(i.$element,"ui-selected"),i.selected=!1,s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",e,{unselecting:i.element})))))}),!1}},_mouseStop:function(e){var i=this;return this.dragged=!1,t(".ui-unselecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",e,{unselected:s.element})}),t(".ui-selecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-selecting")._addClass(s.$element,"ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",e,{selected:s.element})}),this._trigger("stop",e),this.helper.remove(),!1}}),t.widget("ui.selectmenu",[t.ui.formResetMixin,{version:"1.12.1",defaultElement:"<select>",options:{appendTo:null,classes:{"ui-selectmenu-button-open":"ui-corner-top","ui-selectmenu-button-closed":"ui-corner-all"},disabled:null,icons:{button:"ui-icon-triangle-1-s"},position:{my:"left top",at:"left bottom",collision:"none"},width:!1,change:null,close:null,focus:null,open:null,select:null},_create:function(){var e=this.element.uniqueId().attr("id");this.ids={element:e,button:e+"-button",menu:e+"-menu"},this._drawButton(),this._drawMenu(),this._bindFormResetHandler(),this._rendered=!1,this.menuItems=t()},_drawButton:function(){var e,i=this,s=this._parseOption(this.element.find("option:selected"),this.element[0].selectedIndex);this.labels=this.element.labels().attr("for",this.ids.button),this._on(this.labels,{click:function(t){this.button.focus(),t.preventDefault()}}),this.element.hide(),this.button=t("<span>",{tabindex:this.options.disabled?-1:0,id:this.ids.button,role:"combobox","aria-expanded":"false","aria-autocomplete":"list","aria-owns":this.ids.menu,"aria-haspopup":"true",title:this.element.attr("title")}).insertAfter(this.element),this._addClass(this.button,"ui-selectmenu-button ui-selectmenu-button-closed","ui-button ui-widget"),e=t("<span>").appendTo(this.button),this._addClass(e,"ui-selectmenu-icon","ui-icon "+this.options.icons.button),this.buttonItem=this._renderButtonItem(s).appendTo(this.button),this.options.width!==!1&&this._resizeButton(),this._on(this.button,this._buttonEvents),this.button.one("focusin",function(){i._rendered||i._refreshMenu()})},_drawMenu:function(){var e=this;this.menu=t("<ul>",{"aria-hidden":"true","aria-labelledby":this.ids.button,id:this.ids.menu}),this.menuWrap=t("<div>").append(this.menu),this._addClass(this.menuWrap,"ui-selectmenu-menu","ui-front"),this.menuWrap.appendTo(this._appendTo()),this.menuInstance=this.menu.menu({classes:{"ui-menu":"ui-corner-bottom"},role:"listbox",select:function(t,i){t.preventDefault(),e._setSelection(),e._select(i.item.data("ui-selectmenu-item"),t)},focus:function(t,i){var s=i.item.data("ui-selectmenu-item");null!=e.focusIndex&&s.index!==e.focusIndex&&(e._trigger("focus",t,{item:s}),e.isOpen||e._select(s,t)),e.focusIndex=s.index,e.button.attr("aria-activedescendant",e.menuItems.eq(s.index).attr("id"))}}).menu("instance"),this.menuInstance._off(this.menu,"mouseleave"),this.menuInstance._closeOnDocumentClick=function(){return!1},this.menuInstance._isDivider=function(){return!1}},refresh:function(){this._refreshMenu(),this.buttonItem.replaceWith(this.buttonItem=this._renderButtonItem(this._getSelectedItem().data("ui-selectmenu-item")||{})),null===this.options.width&&this._resizeButton()},_refreshMenu:function(){var t,e=this.element.find("option");this.menu.empty(),this._parseOptions(e),this._renderMenu(this.menu,this.items),this.menuInstance.refresh(),this.menuItems=this.menu.find("li").not(".ui-selectmenu-optgroup").find(".ui-menu-item-wrapper"),this._rendered=!0,e.length&&(t=this._getSelectedItem(),this.menuInstance.focus(null,t),this._setAria(t.data("ui-selectmenu-item")),this._setOption("disabled",this.element.prop("disabled")))},open:function(t){this.options.disabled||(this._rendered?(this._removeClass(this.menu.find(".ui-state-active"),null,"ui-state-active"),this.menuInstance.focus(null,this._getSelectedItem())):this._refreshMenu(),this.menuItems.length&&(this.isOpen=!0,this._toggleAttr(),this._resizeMenu(),this._position(),this._on(this.document,this._documentClick),this._trigger("open",t)))},_position:function(){this.menuWrap.position(t.extend({of:this.button},this.options.position))},close:function(t){this.isOpen&&(this.isOpen=!1,this._toggleAttr(),this.range=null,this._off(this.document),this._trigger("close",t))},widget:function(){return this.button},menuWidget:function(){return this.menu},_renderButtonItem:function(e){var i=t("<span>");return this._setText(i,e.label),this._addClass(i,"ui-selectmenu-text"),i},_renderMenu:function(e,i){var s=this,n="";t.each(i,function(i,o){var a;o.optgroup!==n&&(a=t("<li>",{text:o.optgroup}),s._addClass(a,"ui-selectmenu-optgroup","ui-menu-divider"+(o.element.parent("optgroup").prop("disabled")?" ui-state-disabled":"")),a.appendTo(e),n=o.optgroup),s._renderItemData(e,o)})},_renderItemData:function(t,e){return this._renderItem(t,e).data("ui-selectmenu-item",e)},_renderItem:function(e,i){var s=t("<li>"),n=t("<div>",{title:i.element.attr("title")});return i.disabled&&this._addClass(s,null,"ui-state-disabled"),this._setText(n,i.label),s.append(n).appendTo(e)},_setText:function(t,e){e?t.text(e):t.html(" ")},_move:function(t,e){var i,s,n=".ui-menu-item";this.isOpen?i=this.menuItems.eq(this.focusIndex).parent("li"):(i=this.menuItems.eq(this.element[0].selectedIndex).parent("li"),n+=":not(.ui-state-disabled)"),s="first"===t||"last"===t?i["first"===t?"prevAll":"nextAll"](n).eq(-1):i[t+"All"](n).eq(0),s.length&&this.menuInstance.focus(e,s)},_getSelectedItem:function(){return this.menuItems.eq(this.element[0].selectedIndex).parent("li")},_toggle:function(t){this[this.isOpen?"close":"open"](t)},_setSelection:function(){var t;this.range&&(window.getSelection?(t=window.getSelection(),t.removeAllRanges(),t.addRange(this.range)):this.range.select(),this.button.focus())},_documentClick:{mousedown:function(e){this.isOpen&&(t(e.target).closest(".ui-selectmenu-menu, #"+t.ui.escapeSelector(this.ids.button)).length||this.close(e))}},_buttonEvents:{mousedown:function(){var t;window.getSelection?(t=window.getSelection(),t.rangeCount&&(this.range=t.getRangeAt(0))):this.range=document.selection.createRange()},click:function(t){this._setSelection(),this._toggle(t)},keydown:function(e){var i=!0;switch(e.keyCode){case t.ui.keyCode.TAB:case t.ui.keyCode.ESCAPE:this.close(e),i=!1;break;case t.ui.keyCode.ENTER:this.isOpen&&this._selectFocusedItem(e);break;case t.ui.keyCode.UP:e.altKey?this._toggle(e):this._move("prev",e);break;case t.ui.keyCode.DOWN:e.altKey?this._toggle(e):this._move("next",e);break;case t.ui.keyCode.SPACE:this.isOpen?this._selectFocusedItem(e):this._toggle(e);break;case t.ui.keyCode.LEFT:this._move("prev",e);break;case t.ui.keyCode.RIGHT:this._move("next",e);break;case t.ui.keyCode.HOME:case t.ui.keyCode.PAGE_UP:this._move("first",e);break;case t.ui.keyCode.END:case t.ui.keyCode.PAGE_DOWN:this._move("last",e);break;default:this.menu.trigger(e),i=!1}i&&e.preventDefault()}},_selectFocusedItem:function(t){var e=this.menuItems.eq(this.focusIndex).parent("li");e.hasClass("ui-state-disabled")||this._select(e.data("ui-selectmenu-item"),t)},_select:function(t,e){var i=this.element[0].selectedIndex;this.element[0].selectedIndex=t.index,this.buttonItem.replaceWith(this.buttonItem=this._renderButtonItem(t)),this._setAria(t),this._trigger("select",e,{item:t}),t.index!==i&&this._trigger("change",e,{item:t}),this.close(e)},_setAria:function(t){var e=this.menuItems.eq(t.index).attr("id");this.button.attr({"aria-labelledby":e,"aria-activedescendant":e}),this.menu.attr("aria-activedescendant",e)},_setOption:function(t,e){if("icons"===t){var i=this.button.find("span.ui-icon");this._removeClass(i,null,this.options.icons.button)._addClass(i,null,e.button)}this._super(t,e),"appendTo"===t&&this.menuWrap.appendTo(this._appendTo()),"width"===t&&this._resizeButton()},_setOptionDisabled:function(t){this._super(t),this.menuInstance.option("disabled",t),this.button.attr("aria-disabled",t),this._toggleClass(this.button,null,"ui-state-disabled",t),this.element.prop("disabled",t),t?(this.button.attr("tabindex",-1),this.close()):this.button.attr("tabindex",0)},_appendTo:function(){var e=this.options.appendTo;return e&&(e=e.jquery||e.nodeType?t(e):this.document.find(e).eq(0)),e&&e[0]||(e=this.element.closest(".ui-front, dialog")),e.length||(e=this.document[0].body),e},_toggleAttr:function(){this.button.attr("aria-expanded",this.isOpen),this._removeClass(this.button,"ui-selectmenu-button-"+(this.isOpen?"closed":"open"))._addClass(this.button,"ui-selectmenu-button-"+(this.isOpen?"open":"closed"))._toggleClass(this.menuWrap,"ui-selectmenu-open",null,this.isOpen),this.menu.attr("aria-hidden",!this.isOpen)},_resizeButton:function(){var t=this.options.width;return t===!1?(this.button.css("width",""),void 0):(null===t&&(t=this.element.show().outerWidth(),this.element.hide()),this.button.outerWidth(t),void 0)},_resizeMenu:function(){this.menu.outerWidth(Math.max(this.button.outerWidth(),this.menu.width("").outerWidth()+1))},_getCreateOptions:function(){var t=this._super();return t.disabled=this.element.prop("disabled"),t},_parseOptions:function(e){var i=this,s=[];e.each(function(e,n){s.push(i._parseOption(t(n),e))}),this.items=s},_parseOption:function(t,e){var i=t.parent("optgroup");return{element:t,index:e,value:t.val(),label:t.text(),optgroup:i.attr("label")||"",disabled:i.prop("disabled")||t.prop("disabled")}},_destroy:function(){this._unbindFormResetHandler(),this.menuWrap.remove(),this.button.remove(),this.element.show(),this.element.removeUniqueId(),this.labels.attr("for",this.ids.element)}}]),t.widget("ui.slider",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"slide",options:{animate:!1,classes:{"ui-slider":"ui-corner-all","ui-slider-handle":"ui-corner-all","ui-slider-range":"ui-corner-all ui-widget-header"},distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null,change:null,slide:null,start:null,stop:null},numPages:5,_create:function(){this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this._calculateNewMax(),this._addClass("ui-slider ui-slider-"+this.orientation,"ui-widget ui-widget-content"),this._refresh(),this._animateOff=!1 + },_refresh:function(){this._createRange(),this._createHandles(),this._setupEvents(),this._refreshValue()},_createHandles:function(){var e,i,s=this.options,n=this.element.find(".ui-slider-handle"),o="<span tabindex='0'></span>",a=[];for(i=s.values&&s.values.length||1,n.length>i&&(n.slice(i).remove(),n=n.slice(0,i)),e=n.length;i>e;e++)a.push(o);this.handles=n.add(t(a.join("")).appendTo(this.element)),this._addClass(this.handles,"ui-slider-handle","ui-state-default"),this.handle=this.handles.eq(0),this.handles.each(function(e){t(this).data("ui-slider-handle-index",e).attr("tabIndex",0)})},_createRange:function(){var e=this.options;e.range?(e.range===!0&&(e.values?e.values.length&&2!==e.values.length?e.values=[e.values[0],e.values[0]]:t.isArray(e.values)&&(e.values=e.values.slice(0)):e.values=[this._valueMin(),this._valueMin()]),this.range&&this.range.length?(this._removeClass(this.range,"ui-slider-range-min ui-slider-range-max"),this.range.css({left:"",bottom:""})):(this.range=t("<div>").appendTo(this.element),this._addClass(this.range,"ui-slider-range")),("min"===e.range||"max"===e.range)&&this._addClass(this.range,"ui-slider-range-"+e.range)):(this.range&&this.range.remove(),this.range=null)},_setupEvents:function(){this._off(this.handles),this._on(this.handles,this._handleEvents),this._hoverable(this.handles),this._focusable(this.handles)},_destroy:function(){this.handles.remove(),this.range&&this.range.remove(),this._mouseDestroy()},_mouseCapture:function(e){var i,s,n,o,a,r,h,l,c=this,u=this.options;return u.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),i={x:e.pageX,y:e.pageY},s=this._normValueFromMouse(i),n=this._valueMax()-this._valueMin()+1,this.handles.each(function(e){var i=Math.abs(s-c.values(e));(n>i||n===i&&(e===c._lastChangedValue||c.values(e)===u.min))&&(n=i,o=t(this),a=e)}),r=this._start(e,a),r===!1?!1:(this._mouseSliding=!0,this._handleIndex=a,this._addClass(o,null,"ui-state-active"),o.trigger("focus"),h=o.offset(),l=!t(e.target).parents().addBack().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:e.pageX-h.left-o.width()/2,top:e.pageY-h.top-o.height()/2-(parseInt(o.css("borderTopWidth"),10)||0)-(parseInt(o.css("borderBottomWidth"),10)||0)+(parseInt(o.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(e,a,s),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(t){var e={x:t.pageX,y:t.pageY},i=this._normValueFromMouse(e);return this._slide(t,this._handleIndex,i),!1},_mouseStop:function(t){return this._removeClass(this.handles,null,"ui-state-active"),this._mouseSliding=!1,this._stop(t,this._handleIndex),this._change(t,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation="vertical"===this.options.orientation?"vertical":"horizontal"},_normValueFromMouse:function(t){var e,i,s,n,o;return"horizontal"===this.orientation?(e=this.elementSize.width,i=t.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(e=this.elementSize.height,i=t.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),s=i/e,s>1&&(s=1),0>s&&(s=0),"vertical"===this.orientation&&(s=1-s),n=this._valueMax()-this._valueMin(),o=this._valueMin()+s*n,this._trimAlignValue(o)},_uiHash:function(t,e,i){var s={handle:this.handles[t],handleIndex:t,value:void 0!==e?e:this.value()};return this._hasMultipleValues()&&(s.value=void 0!==e?e:this.values(t),s.values=i||this.values()),s},_hasMultipleValues:function(){return this.options.values&&this.options.values.length},_start:function(t,e){return this._trigger("start",t,this._uiHash(e))},_slide:function(t,e,i){var s,n,o=this.value(),a=this.values();this._hasMultipleValues()&&(n=this.values(e?0:1),o=this.values(e),2===this.options.values.length&&this.options.range===!0&&(i=0===e?Math.min(n,i):Math.max(n,i)),a[e]=i),i!==o&&(s=this._trigger("slide",t,this._uiHash(e,i,a)),s!==!1&&(this._hasMultipleValues()?this.values(e,i):this.value(i)))},_stop:function(t,e){this._trigger("stop",t,this._uiHash(e))},_change:function(t,e){this._keySliding||this._mouseSliding||(this._lastChangedValue=e,this._trigger("change",t,this._uiHash(e)))},value:function(t){return arguments.length?(this.options.value=this._trimAlignValue(t),this._refreshValue(),this._change(null,0),void 0):this._value()},values:function(e,i){var s,n,o;if(arguments.length>1)return this.options.values[e]=this._trimAlignValue(i),this._refreshValue(),this._change(null,e),void 0;if(!arguments.length)return this._values();if(!t.isArray(arguments[0]))return this._hasMultipleValues()?this._values(e):this.value();for(s=this.options.values,n=arguments[0],o=0;s.length>o;o+=1)s[o]=this._trimAlignValue(n[o]),this._change(null,o);this._refreshValue()},_setOption:function(e,i){var s,n=0;switch("range"===e&&this.options.range===!0&&("min"===i?(this.options.value=this._values(0),this.options.values=null):"max"===i&&(this.options.value=this._values(this.options.values.length-1),this.options.values=null)),t.isArray(this.options.values)&&(n=this.options.values.length),this._super(e,i),e){case"orientation":this._detectOrientation(),this._removeClass("ui-slider-horizontal ui-slider-vertical")._addClass("ui-slider-"+this.orientation),this._refreshValue(),this.options.range&&this._refreshRange(i),this.handles.css("horizontal"===i?"bottom":"left","");break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":for(this._animateOff=!0,this._refreshValue(),s=n-1;s>=0;s--)this._change(null,s);this._animateOff=!1;break;case"step":case"min":case"max":this._animateOff=!0,this._calculateNewMax(),this._refreshValue(),this._animateOff=!1;break;case"range":this._animateOff=!0,this._refresh(),this._animateOff=!1}},_setOptionDisabled:function(t){this._super(t),this._toggleClass(null,"ui-state-disabled",!!t)},_value:function(){var t=this.options.value;return t=this._trimAlignValue(t)},_values:function(t){var e,i,s;if(arguments.length)return e=this.options.values[t],e=this._trimAlignValue(e);if(this._hasMultipleValues()){for(i=this.options.values.slice(),s=0;i.length>s;s+=1)i[s]=this._trimAlignValue(i[s]);return i}return[]},_trimAlignValue:function(t){if(this._valueMin()>=t)return this._valueMin();if(t>=this._valueMax())return this._valueMax();var e=this.options.step>0?this.options.step:1,i=(t-this._valueMin())%e,s=t-i;return 2*Math.abs(i)>=e&&(s+=i>0?e:-e),parseFloat(s.toFixed(5))},_calculateNewMax:function(){var t=this.options.max,e=this._valueMin(),i=this.options.step,s=Math.round((t-e)/i)*i;t=s+e,t>this.options.max&&(t-=i),this.max=parseFloat(t.toFixed(this._precision()))},_precision:function(){var t=this._precisionOf(this.options.step);return null!==this.options.min&&(t=Math.max(t,this._precisionOf(this.options.min))),t},_precisionOf:function(t){var e=""+t,i=e.indexOf(".");return-1===i?0:e.length-i-1},_valueMin:function(){return this.options.min},_valueMax:function(){return this.max},_refreshRange:function(t){"vertical"===t&&this.range.css({width:"",left:""}),"horizontal"===t&&this.range.css({height:"",bottom:""})},_refreshValue:function(){var e,i,s,n,o,a=this.options.range,r=this.options,h=this,l=this._animateOff?!1:r.animate,c={};this._hasMultipleValues()?this.handles.each(function(s){i=100*((h.values(s)-h._valueMin())/(h._valueMax()-h._valueMin())),c["horizontal"===h.orientation?"left":"bottom"]=i+"%",t(this).stop(1,1)[l?"animate":"css"](c,r.animate),h.options.range===!0&&("horizontal"===h.orientation?(0===s&&h.range.stop(1,1)[l?"animate":"css"]({left:i+"%"},r.animate),1===s&&h.range[l?"animate":"css"]({width:i-e+"%"},{queue:!1,duration:r.animate})):(0===s&&h.range.stop(1,1)[l?"animate":"css"]({bottom:i+"%"},r.animate),1===s&&h.range[l?"animate":"css"]({height:i-e+"%"},{queue:!1,duration:r.animate}))),e=i}):(s=this.value(),n=this._valueMin(),o=this._valueMax(),i=o!==n?100*((s-n)/(o-n)):0,c["horizontal"===this.orientation?"left":"bottom"]=i+"%",this.handle.stop(1,1)[l?"animate":"css"](c,r.animate),"min"===a&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:i+"%"},r.animate),"max"===a&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:100-i+"%"},r.animate),"min"===a&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:i+"%"},r.animate),"max"===a&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:100-i+"%"},r.animate))},_handleEvents:{keydown:function(e){var i,s,n,o,a=t(e.target).data("ui-slider-handle-index");switch(e.keyCode){case t.ui.keyCode.HOME:case t.ui.keyCode.END:case t.ui.keyCode.PAGE_UP:case t.ui.keyCode.PAGE_DOWN:case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(e.preventDefault(),!this._keySliding&&(this._keySliding=!0,this._addClass(t(e.target),null,"ui-state-active"),i=this._start(e,a),i===!1))return}switch(o=this.options.step,s=n=this._hasMultipleValues()?this.values(a):this.value(),e.keyCode){case t.ui.keyCode.HOME:n=this._valueMin();break;case t.ui.keyCode.END:n=this._valueMax();break;case t.ui.keyCode.PAGE_UP:n=this._trimAlignValue(s+(this._valueMax()-this._valueMin())/this.numPages);break;case t.ui.keyCode.PAGE_DOWN:n=this._trimAlignValue(s-(this._valueMax()-this._valueMin())/this.numPages);break;case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:if(s===this._valueMax())return;n=this._trimAlignValue(s+o);break;case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(s===this._valueMin())return;n=this._trimAlignValue(s-o)}this._slide(e,a,n)},keyup:function(e){var i=t(e.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(e,i),this._change(e,i),this._removeClass(t(e.target),null,"ui-state-active"))}}}),t.widget("ui.sortable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(t,e,i){return t>=e&&e+i>t},_isFloating:function(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))},_create:function(){this.containerCache={},this._addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(t,e){this._super(t,e),"handle"===t&&this._setHandleClassName()},_setHandleClassName:function(){var e=this;this._removeClass(this.element.find(".ui-sortable-handle"),"ui-sortable-handle"),t.each(this.items,function(){e._addClass(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item,"ui-sortable-handle")})},_destroy:function(){this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):void 0}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("<style>*{ cursor: "+a.cursor+" !important; }</style>").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<a.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+a.scrollSpeed:e.pageY-this.overflowOffset.top<a.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-a.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<a.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+a.scrollSpeed:e.pageX-this.overflowOffset.left<a.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-a.scrollSpeed)):(e.pageY-this.document.scrollTop()<a.scrollSensitivity?r=this.document.scrollTop(this.document.scrollTop()-a.scrollSpeed):this.window.height()-(e.pageY-this.document.scrollTop())<a.scrollSensitivity&&(r=this.document.scrollTop(this.document.scrollTop()+a.scrollSpeed)),e.pageX-this.document.scrollLeft()<a.scrollSensitivity?r=this.document.scrollLeft(this.document.scrollLeft()-a.scrollSpeed):this.window.width()-(e.pageX-this.document.scrollLeft())<a.scrollSensitivity&&(r=this.document.scrollLeft(this.document.scrollLeft()+a.scrollSpeed))),r!==!1&&t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)),this.positionAbs=this._convertPositionTo("absolute"),this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp(new t.Event("mouseup",{target:null})),"original"===this.options.helper?(this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+l>r&&h>s+l,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var e,i,s="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),n="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width),o=s&&n;return o?(e=this._getDragVerticalDirection(),i=this._getDragHorizontalDirection(),this.floating?"right"===i||"down"===e?2:1:e&&("down"===e?2:1)):!1},_intersectsWithSides:function(t){var e=this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&e||"up"===s&&!e)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],h=[],l=this._connectWith();if(l&&e)for(s=l.length-1;s>=0;s--)for(o=t(l[s],this.document[0]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&h.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(h.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i],this.document[0]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+"-item",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]);return e._addClass(n,"ui-sortable-placeholder",i||e.currentItem[0].className)._removeClass(n,"ui-sortable-helper"),"tbody"===s?e._createTrPlaceholder(e.currentItem.find("tr").eq(0),t("<tr>",e.document[0]).appendTo(n)):"tr"===s?e._createTrPlaceholder(e.currentItem,n):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_createTrPlaceholder:function(e,i){var s=this;e.children().each(function(){t("<td> </td>",s.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(e){var i,s,n,o,a,r,h,l,c,u,d=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!t.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(d&&t.contains(this.containers[i].element[0],d.element[0]))continue;d=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",e,this._uiHash(this)),this.containers[i].containerCache.over=0);if(d)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,o=null,c=d.floating||this._isFloating(this.currentItem),a=c?"left":"top",r=c?"width":"height",u=c?"pageX":"pageY",s=this.items.length-1;s>=0;s--)t.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(h=this.items[s].item.offset()[a],l=!1,e[u]-h>this.items[s][r]/2&&(l=!0),n>Math.abs(e[u]-h)&&(n=Math.abs(e[u]-h),o=this.items[s],this.direction=l?"up":"down"));if(!o&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;o?this._rearrange(e,o,null,!0):this._rearrange(e,null,this.containers[p].element,!0),this._trigger("change",e,this._uiHash()),this.containers[p]._trigger("change",e,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.height()||document.body.parentNode.scrollHeight:this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.left<this.containment[0]&&(o=this.containment[0]+this.offset.click.left),e.pageY-this.offset.click.top<this.containment[1]&&(a=this.containment[1]+this.offset.click.top),e.pageX-this.offset.click.left>this.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter; + this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}}),t.widget("ui.spinner",{version:"1.12.1",defaultElement:"<input>",widgetEventPrefix:"spin",options:{classes:{"ui-spinner":"ui-corner-all","ui-spinner-down":"ui-corner-br","ui-spinner-up":"ui-corner-tr"},culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var e=this._super(),i=this.element;return t.each(["min","max","step"],function(t,s){var n=i.attr(s);null!=n&&n.length&&(e[s]=n)}),e},_events:{keydown:function(t){this._start(t)&&this._keydown(t)&&t.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",t),void 0)},mousewheel:function(t,e){if(e){if(!this.spinning&&!this._start(t))return!1;this._spin((e>0?1:-1)*this.options.step,t),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(t)},100),t.preventDefault()}},"mousedown .ui-spinner-button":function(e){function i(){var e=this.element[0]===t.ui.safeActiveElement(this.document[0]);e||(this.element.trigger("focus"),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===t.ui.safeActiveElement(this.document[0])?this.previous:this.element.val(),e.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(e)!==!1&&this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(e){return t(e.currentTarget).hasClass("ui-state-active")?this._start(e)===!1?!1:(this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_enhance:function(){this.uiSpinner=this.element.attr("autocomplete","off").wrap("<span>").parent().append("<a></a><a></a>")},_draw:function(){this._enhance(),this._addClass(this.uiSpinner,"ui-spinner","ui-widget ui-widget-content"),this._addClass("ui-spinner-input"),this.element.attr("role","spinbutton"),this.buttons=this.uiSpinner.children("a").attr("tabIndex",-1).attr("aria-hidden",!0).button({classes:{"ui-button":""}}),this._removeClass(this.buttons,"ui-corner-all"),this._addClass(this.buttons.first(),"ui-spinner-button ui-spinner-up"),this._addClass(this.buttons.last(),"ui-spinner-button ui-spinner-down"),this.buttons.first().button({icon:this.options.icons.up,showLabel:!1}),this.buttons.last().button({icon:this.options.icons.down,showLabel:!1}),this.buttons.height()>Math.ceil(.5*this.uiSpinner.height())&&this.uiSpinner.height()>0&&this.uiSpinner.height(this.uiSpinner.height())},_keydown:function(e){var i=this.options,s=t.ui.keyCode;switch(e.keyCode){case s.UP:return this._repeat(null,1,e),!0;case s.DOWN:return this._repeat(null,-1,e),!0;case s.PAGE_UP:return this._repeat(null,i.page,e),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,e),!0}return!1},_start:function(t){return this.spinning||this._trigger("start",t)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(t,e,i){t=t||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,e,i)},t),this._spin(e*this.options.step,i)},_spin:function(t,e){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+t*this._increment(this.counter)),this.spinning&&this._trigger("spin",e,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(e){var i=this.options.incremental;return i?t.isFunction(i)?i(e):Math.floor(e*e*e/5e4-e*e/500+17*e/200+1):1},_precision:function(){var t=this._precisionOf(this.options.step);return null!==this.options.min&&(t=Math.max(t,this._precisionOf(this.options.min))),t},_precisionOf:function(t){var e=""+t,i=e.indexOf(".");return-1===i?0:e.length-i-1},_adjustValue:function(t){var e,i,s=this.options;return e=null!==s.min?s.min:0,i=t-e,i=Math.round(i/s.step)*s.step,t=e+i,t=parseFloat(t.toFixed(this._precision())),null!==s.max&&t>s.max?s.max:null!==s.min&&s.min>t?s.min:t},_stop:function(t){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",t))},_setOption:function(t,e){var i,s,n;return"culture"===t||"numberFormat"===t?(i=this._parse(this.element.val()),this.options[t]=e,this.element.val(this._format(i)),void 0):(("max"===t||"min"===t||"step"===t)&&"string"==typeof e&&(e=this._parse(e)),"icons"===t&&(s=this.buttons.first().find(".ui-icon"),this._removeClass(s,null,this.options.icons.up),this._addClass(s,null,e.up),n=this.buttons.last().find(".ui-icon"),this._removeClass(n,null,this.options.icons.down),this._addClass(n,null,e.down)),this._super(t,e),void 0)},_setOptionDisabled:function(t){this._super(t),this._toggleClass(this.uiSpinner,null,"ui-state-disabled",!!t),this.element.prop("disabled",!!t),this.buttons.button(t?"disable":"enable")},_setOptions:r(function(t){this._super(t)}),_parse:function(t){return"string"==typeof t&&""!==t&&(t=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(t,10,this.options.culture):+t),""===t||isNaN(t)?null:t},_format:function(t){return""===t?"":window.Globalize&&this.options.numberFormat?Globalize.format(t,this.options.numberFormat,this.options.culture):t},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},isValid:function(){var t=this.value();return null===t?!1:t===this._adjustValue(t)},_value:function(t,e){var i;""!==t&&(i=this._parse(t),null!==i&&(e||(i=this._adjustValue(i)),t=this._format(i))),this.element.val(t),this._refresh()},_destroy:function(){this.element.prop("disabled",!1).removeAttr("autocomplete role aria-valuemin aria-valuemax aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:r(function(t){this._stepUp(t)}),_stepUp:function(t){this._start()&&(this._spin((t||1)*this.options.step),this._stop())},stepDown:r(function(t){this._stepDown(t)}),_stepDown:function(t){this._start()&&(this._spin((t||1)*-this.options.step),this._stop())},pageUp:r(function(t){this._stepUp((t||1)*this.options.page)}),pageDown:r(function(t){this._stepDown((t||1)*this.options.page)}),value:function(t){return arguments.length?(r(this._value).call(this,t),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}}),t.uiBackCompat!==!1&&t.widget("ui.spinner",t.ui.spinner,{_enhance:function(){this.uiSpinner=this.element.attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml())},_uiSpinnerHtml:function(){return"<span>"},_buttonHtml:function(){return"<a></a><a></a>"}}),t.ui.spinner,t.widget("ui.tabs",{version:"1.12.1",delay:300,options:{active:null,classes:{"ui-tabs":"ui-corner-all","ui-tabs-nav":"ui-corner-all","ui-tabs-panel":"ui-corner-bottom","ui-tabs-tab":"ui-corner-top"},collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:function(){var t=/#.*$/;return function(e){var i,s;i=e.href.replace(t,""),s=location.href.replace(t,"");try{i=decodeURIComponent(i)}catch(n){}try{s=decodeURIComponent(s)}catch(n){}return e.hash.length>1&&i===s}}(),_create:function(){var e=this,i=this.options;this.running=!1,this._addClass("ui-tabs","ui-widget ui-widget-content"),this._toggleClass("ui-tabs-collapsible",null,i.collapsible),this._processTabs(),i.active=this._initialActive(),t.isArray(i.disabled)&&(i.disabled=t.unique(i.disabled.concat(t.map(this.tabs.filter(".ui-state-disabled"),function(t){return e.tabs.index(t)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):t(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var e=this.options.active,i=this.options.collapsible,s=location.hash.substring(1);return null===e&&(s&&this.tabs.each(function(i,n){return t(n).attr("aria-controls")===s?(e=i,!1):void 0}),null===e&&(e=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===e||-1===e)&&(e=this.tabs.length?0:!1)),e!==!1&&(e=this.tabs.index(this.tabs.eq(e)),-1===e&&(e=i?!1:0)),!i&&e===!1&&this.anchors.length&&(e=0),e},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):t()}},_tabKeydown:function(e){var i=t(t.ui.safeActiveElement(this.document[0])).closest("li"),s=this.tabs.index(i),n=!0;if(!this._handlePageNav(e)){switch(e.keyCode){case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:s++;break;case t.ui.keyCode.UP:case t.ui.keyCode.LEFT:n=!1,s--;break;case t.ui.keyCode.END:s=this.anchors.length-1;break;case t.ui.keyCode.HOME:s=0;break;case t.ui.keyCode.SPACE:return e.preventDefault(),clearTimeout(this.activating),this._activate(s),void 0;case t.ui.keyCode.ENTER:return e.preventDefault(),clearTimeout(this.activating),this._activate(s===this.options.active?!1:s),void 0;default:return}e.preventDefault(),clearTimeout(this.activating),s=this._focusNextTab(s,n),e.ctrlKey||e.metaKey||(i.attr("aria-selected","false"),this.tabs.eq(s).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",s)},this.delay))}},_panelKeydown:function(e){this._handlePageNav(e)||e.ctrlKey&&e.keyCode===t.ui.keyCode.UP&&(e.preventDefault(),this.active.trigger("focus"))},_handlePageNav:function(e){return e.altKey&&e.keyCode===t.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):e.altKey&&e.keyCode===t.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):void 0},_findNextTab:function(e,i){function s(){return e>n&&(e=0),0>e&&(e=n),e}for(var n=this.tabs.length-1;-1!==t.inArray(s(),this.options.disabled);)e=i?e+1:e-1;return e},_focusNextTab:function(t,e){return t=this._findNextTab(t,e),this.tabs.eq(t).trigger("focus"),t},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):(this._super(t,e),"collapsible"===t&&(this._toggleClass("ui-tabs-collapsible",null,e),e||this.options.active!==!1||this._activate(0)),"event"===t&&this._setupEvents(e),"heightStyle"===t&&this._setupHeightStyle(e),void 0)},_sanitizeSelector:function(t){return t?t.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var e=this.options,i=this.tablist.children(":has(a[href])");e.disabled=t.map(i.filter(".ui-state-disabled"),function(t){return i.index(t)}),this._processTabs(),e.active!==!1&&this.anchors.length?this.active.length&&!t.contains(this.tablist[0],this.active[0])?this.tabs.length===e.disabled.length?(e.active=!1,this.active=t()):this._activate(this._findNextTab(Math.max(0,e.active-1),!1)):e.active=this.tabs.index(this.active):(e.active=!1,this.active=t()),this._refresh()},_refresh:function(){this._setOptionDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-hidden":"true"}),this.active.length?(this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}),this._addClass(this.active,"ui-tabs-active","ui-state-active"),this._getPanelForTab(this.active).show().attr({"aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var e=this,i=this.tabs,s=this.anchors,n=this.panels;this.tablist=this._getList().attr("role","tablist"),this._addClass(this.tablist,"ui-tabs-nav","ui-helper-reset ui-helper-clearfix ui-widget-header"),this.tablist.on("mousedown"+this.eventNamespace,"> li",function(e){t(this).is(".ui-state-disabled")&&e.preventDefault()}).on("focus"+this.eventNamespace,".ui-tabs-anchor",function(){t(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this.tabs=this.tablist.find("> li:has(a[href])").attr({role:"tab",tabIndex:-1}),this._addClass(this.tabs,"ui-tabs-tab","ui-state-default"),this.anchors=this.tabs.map(function(){return t("a",this)[0]}).attr({role:"presentation",tabIndex:-1}),this._addClass(this.anchors,"ui-tabs-anchor"),this.panels=t(),this.anchors.each(function(i,s){var n,o,a,r=t(s).uniqueId().attr("id"),h=t(s).closest("li"),l=h.attr("aria-controls");e._isLocal(s)?(n=s.hash,a=n.substring(1),o=e.element.find(e._sanitizeSelector(n))):(a=h.attr("aria-controls")||t({}).uniqueId()[0].id,n="#"+a,o=e.element.find(n),o.length||(o=e._createPanel(a),o.insertAfter(e.panels[i-1]||e.tablist)),o.attr("aria-live","polite")),o.length&&(e.panels=e.panels.add(o)),l&&h.data("ui-tabs-aria-controls",l),h.attr({"aria-controls":a,"aria-labelledby":r}),o.attr("aria-labelledby",r)}),this.panels.attr("role","tabpanel"),this._addClass(this.panels,"ui-tabs-panel","ui-widget-content"),i&&(this._off(i.not(this.tabs)),this._off(s.not(this.anchors)),this._off(n.not(this.panels)))},_getList:function(){return this.tablist||this.element.find("ol, ul").eq(0)},_createPanel:function(e){return t("<div>").attr("id",e).data("ui-tabs-destroy",!0)},_setOptionDisabled:function(e){var i,s,n;for(t.isArray(e)&&(e.length?e.length===this.anchors.length&&(e=!0):e=!1),n=0;s=this.tabs[n];n++)i=t(s),e===!0||-1!==t.inArray(n,e)?(i.attr("aria-disabled","true"),this._addClass(i,null,"ui-state-disabled")):(i.removeAttr("aria-disabled"),this._removeClass(i,null,"ui-state-disabled"));this.options.disabled=e,this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,e===!0)},_setupEvents:function(e){var i={};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(t){t.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(e){var i,s=this.element.parent();"fill"===e?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var e=t(this),s=e.css("position");"absolute"!==s&&"fixed"!==s&&(i-=e.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=t(this).outerHeight(!0)}),this.panels.each(function(){t(this).height(Math.max(0,i-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===e&&(i=0,this.panels.each(function(){i=Math.max(i,t(this).height("").height())}).height(i))},_eventHandler:function(e){var i=this.options,s=this.active,n=t(e.currentTarget),o=n.closest("li"),a=o[0]===s[0],r=a&&i.collapsible,h=r?t():this._getPanelForTab(o),l=s.length?this._getPanelForTab(s):t(),c={oldTab:s,oldPanel:l,newTab:r?t():o,newPanel:h};e.preventDefault(),o.hasClass("ui-state-disabled")||o.hasClass("ui-tabs-loading")||this.running||a&&!i.collapsible||this._trigger("beforeActivate",e,c)===!1||(i.active=r?!1:this.tabs.index(o),this.active=a?t():o,this.xhr&&this.xhr.abort(),l.length||h.length||t.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(o),e),this._toggle(e,c))},_toggle:function(e,i){function s(){o.running=!1,o._trigger("activate",e,i)}function n(){o._addClass(i.newTab.closest("li"),"ui-tabs-active","ui-state-active"),a.length&&o.options.show?o._show(a,o.options.show,s):(a.show(),s())}var o=this,a=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){o._removeClass(i.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),n()}):(this._removeClass(i.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),r.hide(),n()),r.attr("aria-hidden","true"),i.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),a.length&&r.length?i.oldTab.attr("tabIndex",-1):a.length&&this.tabs.filter(function(){return 0===t(this).attr("tabIndex")}).attr("tabIndex",-1),a.attr("aria-hidden","false"),i.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(e){var i,s=this._findActive(e);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return e===!1?t():this.tabs.eq(e)},_getIndex:function(e){return"string"==typeof e&&(e=this.anchors.index(this.anchors.filter("[href$='"+t.ui.escapeSelector(e)+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.tablist.removeAttr("role").off(this.eventNamespace),this.anchors.removeAttr("role tabIndex").removeUniqueId(),this.tabs.add(this.panels).each(function(){t.data(this,"ui-tabs-destroy")?t(this).remove():t(this).removeAttr("role tabIndex aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded")}),this.tabs.each(function(){var e=t(this),i=e.data("ui-tabs-aria-controls");i?e.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):e.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(e){var i=this.options.disabled;i!==!1&&(void 0===e?i=!1:(e=this._getIndex(e),i=t.isArray(i)?t.map(i,function(t){return t!==e?t:null}):t.map(this.tabs,function(t,i){return i!==e?i:null})),this._setOptionDisabled(i))},disable:function(e){var i=this.options.disabled;if(i!==!0){if(void 0===e)i=!0;else{if(e=this._getIndex(e),-1!==t.inArray(e,i))return;i=t.isArray(i)?t.merge([e],i).sort():[e]}this._setOptionDisabled(i)}},load:function(e,i){e=this._getIndex(e);var s=this,n=this.tabs.eq(e),o=n.find(".ui-tabs-anchor"),a=this._getPanelForTab(n),r={tab:n,panel:a},h=function(t,e){"abort"===e&&s.panels.stop(!1,!0),s._removeClass(n,"ui-tabs-loading"),a.removeAttr("aria-busy"),t===s.xhr&&delete s.xhr};this._isLocal(o[0])||(this.xhr=t.ajax(this._ajaxSettings(o,i,r)),this.xhr&&"canceled"!==this.xhr.statusText&&(this._addClass(n,"ui-tabs-loading"),a.attr("aria-busy","true"),this.xhr.done(function(t,e,n){setTimeout(function(){a.html(t),s._trigger("load",i,r),h(n,e)},1)}).fail(function(t,e){setTimeout(function(){h(t,e)},1)})))},_ajaxSettings:function(e,i,s){var n=this;return{url:e.attr("href").replace(/#.*$/,""),beforeSend:function(e,o){return n._trigger("beforeLoad",i,t.extend({jqXHR:e,ajaxSettings:o},s))}}},_getPanelForTab:function(e){var i=t(e).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}}),t.uiBackCompat!==!1&&t.widget("ui.tabs",t.ui.tabs,{_processTabs:function(){this._superApply(arguments),this._addClass(this.tabs,"ui-tab")}}),t.ui.tabs,t.widget("ui.tooltip",{version:"1.12.1",options:{classes:{"ui-tooltip":"ui-corner-all ui-widget-shadow"},content:function(){var e=t(this).attr("title")||"";return t("<a>").text(e).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,track:!1,close:null,open:null},_addDescribedBy:function(e,i){var s=(e.attr("aria-describedby")||"").split(/\s+/);s.push(i),e.data("ui-tooltip-id",i).attr("aria-describedby",t.trim(s.join(" ")))},_removeDescribedBy:function(e){var i=e.data("ui-tooltip-id"),s=(e.attr("aria-describedby")||"").split(/\s+/),n=t.inArray(i,s);-1!==n&&s.splice(n,1),e.removeData("ui-tooltip-id"),s=t.trim(s.join(" ")),s?e.attr("aria-describedby",s):e.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.liveRegion=t("<div>").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this.disabledTitles=t([])},_setOption:function(e,i){var s=this;this._super(e,i),"content"===e&&t.each(this.tooltips,function(t,e){s._updateContent(e.element)})},_setOptionDisabled:function(t){this[t?"_disable":"_enable"]()},_disable:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur");n.target=n.currentTarget=s.element[0],e.close(n,!0)}),this.disabledTitles=this.disabledTitles.add(this.element.find(this.options.items).addBack().filter(function(){var e=t(this);return e.is("[title]")?e.data("ui-tooltip-title",e.attr("title")).removeAttr("title"):void 0}))},_enable:function(){this.disabledTitles.each(function(){var e=t(this);e.data("ui-tooltip-title")&&e.attr("title",e.data("ui-tooltip-title"))}),this.disabledTitles=t([])},open:function(e){var i=this,s=t(e?e.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),e&&"mouseover"===e.type&&s.parents().each(function(){var e,s=t(this);s.data("ui-tooltip-open")&&(e=t.Event("blur"),e.target=e.currentTarget=this,i.close(e,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._registerCloseHandlers(e,s),this._updateContent(s,e))},_updateContent:function(t,e){var i,s=this.options.content,n=this,o=e?e.type:null;return"string"==typeof s||s.nodeType||s.jquery?this._open(e,t,s):(i=s.call(t[0],function(i){n._delay(function(){t.data("ui-tooltip-open")&&(e&&(e.type=o),this._open(e,t,i))})}),i&&this._open(e,t,i),void 0)},_open:function(e,i,s){function n(t){l.of=t,a.is(":hidden")||a.position(l)}var o,a,r,h,l=t.extend({},this.options.position);if(s){if(o=this._find(i))return o.tooltip.find(".ui-tooltip-content").html(s),void 0;i.is("[title]")&&(e&&"mouseover"===e.type?i.attr("title",""):i.removeAttr("title")),o=this._tooltip(i),a=o.tooltip,this._addDescribedBy(i,a.attr("id")),a.find(".ui-tooltip-content").html(s),this.liveRegion.children().hide(),h=t("<div>").html(a.find(".ui-tooltip-content").html()),h.removeAttr("name").find("[name]").removeAttr("name"),h.removeAttr("id").find("[id]").removeAttr("id"),h.appendTo(this.liveRegion),this.options.track&&e&&/^mouse/.test(e.type)?(this._on(this.document,{mousemove:n}),n(e)):a.position(t.extend({of:i},this.options.position)),a.hide(),this._show(a,this.options.show),this.options.track&&this.options.show&&this.options.show.delay&&(r=this.delayedShow=setInterval(function(){a.is(":visible")&&(n(l.of),clearInterval(r))},t.fx.interval)),this._trigger("open",e,{tooltip:a})}},_registerCloseHandlers:function(e,i){var s={keyup:function(e){if(e.keyCode===t.ui.keyCode.ESCAPE){var s=t.Event(e);s.currentTarget=i[0],this.close(s,!0)}}};i[0]!==this.element[0]&&(s.remove=function(){this._removeTooltip(this._find(i).tooltip)}),e&&"mouseover"!==e.type||(s.mouseleave="close"),e&&"focusin"!==e.type||(s.focusout="close"),this._on(!0,i,s)},close:function(e){var i,s=this,n=t(e?e.currentTarget:this.element),o=this._find(n);return o?(i=o.tooltip,o.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&!n.attr("title")&&n.attr("title",n.data("ui-tooltip-title")),this._removeDescribedBy(n),o.hiding=!0,i.stop(!0),this._hide(i,this.options.hide,function(){s._removeTooltip(t(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),e&&"mouseleave"===e.type&&t.each(this.parents,function(e,i){t(i.element).attr("title",i.title),delete s.parents[e]}),o.closing=!0,this._trigger("close",e,{tooltip:i}),o.hiding||(o.closing=!1)),void 0):(n.removeData("ui-tooltip-open"),void 0)},_tooltip:function(e){var i=t("<div>").attr("role","tooltip"),s=t("<div>").appendTo(i),n=i.uniqueId().attr("id");return this._addClass(s,"ui-tooltip-content"),this._addClass(i,"ui-tooltip","ui-widget ui-widget-content"),i.appendTo(this._appendTo(e)),this.tooltips[n]={element:e,tooltip:i}},_find:function(t){var e=t.data("ui-tooltip-id");return e?this.tooltips[e]:null},_removeTooltip:function(t){t.remove(),delete this.tooltips[t.attr("id")]},_appendTo:function(t){var e=t.closest(".ui-front, dialog");return e.length||(e=this.document[0].body),e},_destroy:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur"),o=s.element;n.target=n.currentTarget=o[0],e.close(n,!0),t("#"+i).remove(),o.data("ui-tooltip-title")&&(o.attr("title")||o.attr("title",o.data("ui-tooltip-title")),o.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}}),t.uiBackCompat!==!1&&t.widget("ui.tooltip",t.ui.tooltip,{options:{tooltipClass:null},_tooltip:function(){var t=this._superApply(arguments);return this.options.tooltipClass&&t.tooltip.addClass(this.options.tooltipClass),t}}),t.ui.tooltip}); \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryValidate.js b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryValidate.js new file mode 100644 index 0000000000000000000000000000000000000000..38143368a026cce7a6a82e59542aaef91405b44b --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/libs/jquery/jqueryValidate.js @@ -0,0 +1,4 @@ +/*! jQuery Validation Plugin - v1.14.0 - 6/30/2015 + * http://jqueryvalidation.org/ + * Copyright (c) 2015 Jörn Zaefferer; Licensed MIT */ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.on("click.validate",":submit",function(b){c.settings.submitHandler&&(c.submitButton=b.target),a(this).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(this).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.on("submit.validate",function(b){function d(){var d,e;return c.settings.submitHandler?(c.submitButton&&(d=a("<input type='hidden'/>").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),e=c.settings.submitHandler.call(c,c.currentForm,b),c.submitButton&&d.remove(),void 0!==e?e:!1):!0}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,d=d.concat(c.errorList)}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0];if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(b,c){i[c]=f[c],delete f[c],"required"===c&&a(j).removeAttr("aria-required")}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g),a(j).attr("aria-required","true")),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}),a.extend(a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){return!!a.trim(""+a(b).val())},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||-1!==a.inArray(c.keyCode,d)||(b.name in this.submitted||b===this.lastElement)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date ( ISO ).",number:"Please enter a valid number.",digits:"Please enter only digits.",creditcard:"Please enter a valid credit card number.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c=a.data(this.form,"validator"),d="on"+b.type.replace(/^validate/,""),e=c.settings;e[d]&&!a(this).is(e.ignore)&&e[d].call(c,this,b)}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){d[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler),a(this.currentForm).find("[required], [data-rule-required], .required").attr("aria-required","true")},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c=this.clean(b),d=this.validationTargetFor(c),e=!0;return this.lastElement=d,void 0===d?delete this.invalid[c.name]:(this.prepareElement(d),this.currentElements=a(d),e=this.check(d)!==!1,e?delete this.invalid[d.name]:this.invalid[d.name]=!0),a(b).attr("aria-invalid",!e),this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),e},showErrors:function(b){if(b){a.extend(this.errorMap,b),this.errorList=[];for(var c in b)this.errorList.push({message:b[c],element:this.findByName(c)[0]});this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.submitted={},this.lastElement=null,this.prepareForm(),this.hideErrors();var b,c=this.elements().removeData("previousValue").removeAttr("aria-invalid");if(this.settings.unhighlight)for(b=0;c[b];b++)this.settings.unhighlight.call(this,c[b],this.settings.errorClass,"");else c.removeClass(this.settings.errorClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){return!this.name&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.name in c||!b.objectLength(a(this).rules())?!1:(c[this.name]=!0,!0)})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},reset:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([]),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d=a(b),e=b.type;return"radio"===e||"checkbox"===e?this.findByName(b.name).filter(":checked").val():"number"===e&&"undefined"!=typeof b.validity?b.validity.badInput?!1:d.val():(c=d.val(),"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f=a(b).rules(),g=a.map(f,function(a,b){return b}).length,h=!1,i=this.elementValue(b);for(d in f){e={method:d,parameters:f[d]};try{if(c=a.validator.methods[d].call(this,i,b,e.parameters),"dependency-mismatch"===c&&1===g){h=!0;continue}if(h=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(j){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",j),j instanceof TypeError&&(j.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),j}}if(!h)return this.objectLength(f)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;a<arguments.length;a++)if(void 0!==arguments[a])return arguments[a];return void 0},defaultMessage:function(b,c){return this.findDefined(this.customMessage(b.name,c),this.customDataMessage(b,c),!this.settings.ignoreTitle&&b.title||void 0,a.validator.messages[c],"<strong>Warning: No message defined for "+b.name+"</strong>")},formatAndAdd:function(b,c){var d=this.defaultMessage(b,c.method),e=/\$?\{(\d+)\}/g;"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),this.errorList.push({message:d,element:b,method:c.method}),this.errorMap[b.name]=d,this.submitted[b.name]=d},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g=this.errorsFor(b),h=this.idOrName(b),i=a(b).attr("aria-describedby");g.length?(g.removeClass(this.settings.validClass).addClass(this.settings.errorClass),g.html(c)):(g=a("<"+this.settings.errorElement+">").attr("id",h+"-error").addClass(this.settings.errorClass).html(c||""),d=g,this.settings.wrapper&&(d=g.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement(d,a(b)):d.insertAfter(b),g.is("label")?g.attr("for",h):0===g.parents("label[for='"+h+"']").length&&(f=g.attr("id").replace(/(:|\.|\[|\]|\$)/g,"\\$1"),i?i.match(new RegExp("\\b"+f+"\\b"))||(i+=" "+f):i=f,a(b).attr("aria-describedby",i),e=this.groups[b.name],e&&a.each(this.groups,function(b,c){c===e&&a("[name='"+b+"']",this.currentForm).attr("aria-describedby",g.attr("id"))}))),!c&&this.settings.success&&(g.text(""),"string"==typeof this.settings.success?g.addClass(this.settings.success):this.settings.success(g,b)),this.toShow=this.toShow.add(g)},errorsFor:function(b){var c=this.idOrName(b),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+d.replace(/\s+/g,", #")),this.errors().filter(e)},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+b+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return this.dependTypes[typeof a]?this.dependTypes[typeof a](a,b):!0},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(a){this.pending[a.name]||(this.pendingRequest++,this.pending[a.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b){return a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,"remote")})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a[c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0!==e.param?e.param:!0:delete b[d]}}),a.each(b,function(d,e){b[d]=a.isFunction(e)?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(a)},date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a).toString())},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},creditcard:function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9 \-]+/.test(a))return!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1;c>=0;c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||d>=e},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||c>=a},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.off(".validate-equalTo").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d){if(this.optional(c))return"dependency-mismatch";var e,f,g=this.previousValue(c);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),g.originalMessage=this.settings.messages[c.name].remote,this.settings.messages[c.name].remote=g.message,d="string"==typeof d&&{url:d}||d,g.old===b?g.valid:(g.old=b,e=this,this.startRequest(c),f={},f[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:f,context:e.currentForm,success:function(d){var f,h,i,j=d===!0||"true"===d;e.settings.messages[c.name].remote=g.originalMessage,j?(i=e.formSubmitted,e.prepareElement(c),e.formSubmitted=i,e.successList.push(c),delete e.invalid[c.name],e.showErrors()):(f={},h=d||e.defaultMessage(c,"remote"),f[c.name]=g.message=a.isFunction(h)?h(b):h,e.invalid[c.name]=!0,e.showErrors(f)),g.valid=j,e.stopRequest(c,j)}},d)),"pending")}}});var b,c={};a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)})}); \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/profile/profile.jsp b/gemeinsamforschen/src/main/webapp/profile/profile.jsp index cb935e74e5bd5191f40e4b12660ba00adf37f496..0c2f4270b3847d9a6f238f14cce5ef52fd14f554 100644 --- a/gemeinsamforschen/src/main/webapp/profile/profile.jsp +++ b/gemeinsamforschen/src/main/webapp/profile/profile.jsp @@ -1,16 +1,7 @@ -<%@ page import="unipotsdam.gf.modules.project.ManagementImpl" %> -<%@ page import="unipotsdam.gf.modules.user.User" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> -<%@ page import="unipotsdam.gf.core.database.mysql.MysqlConnect" %> -<%@ page import="unipotsdam.gf.core.management.user.User" %> -<%@ page import="unipotsdam.gf.core.management.user.UserDAO" %> -<%@ taglib uri="../core/gemeinsamForschen.tld" prefix="menu" %> -<%@ taglib uri="../core/gemeinsamForschen.tld" prefix="headLine" %> -<%@ taglib uri="../core/gemeinsamForschen.tld" prefix="omniDependencies" %> -<%@ taglib uri="../core/gemeinsamForschen.tld" prefix="footer" %> <!DOCTYPE html> @@ -23,107 +14,97 @@ <script src="js/profile.js"></script> </head> - -<% - // Retrieve user to be used here - String token = request.getParameter("token"); - UserDAO userDAO = new UserDAO(new MysqlConnect()); - User user = userDAO.getUserByToken(token); -%> - <body> -<div id="wrapper"> - <menu:menu hierarchy="1"/> +<menu:menu hierarchy="1"/> - <div class="page-content-wrapper"> - <headLine:headLine/> - <div class="container"> - <div class="row"> - <%-- about --%> - <div class="col-sm-4"> - <h3>Über mich</h3> - <%-- TODO: retrieve profile data --%> - <ul class="list-group"> - <li class="list-group-item"> - <p>Name: <%=user.getName()%></p> - </li> - <li class="list-group-item"> - <p>Sonstiges:</p> - <form id="uploadimage" method="post" action="rest/user/student/wiepke"> - <div id="image_preview"><img id="previewing" src="../libs/img/noImg.png"/></div> - <hr id="line"> - <div id="selectImage"> - <label>Select Your Image</label><br/> - <input type="file" name="image" id="file" required/> - <input type="submit" value="Upload" class="submit"/> - </div> - </form> - <p id="message"></p> - </li> - </ul> +<div class="page-content-wrapper"> + <headLine:headLine/> + <div class="container"> + <div class="row"> + <%-- about --%> + <div class="col-sm-4"> + <h3>Über mich</h3> + <%-- TODO: retrieve profile data --%> + <ul class="list-group"> + <li class="list-group-item"> - </div> + </li> + <li class="list-group-item"> + <p>Sonstiges:</p> + <form id="uploadimage" method="post" action="rest/user/student/wiepke"> + <div id="image_preview"><img id="previewing" src="../libs/img/noImg.png"/></div> + <hr id="line"> + <div id="selectImage"> + <label>Select Your Image</label><br/> + <input type="file" name="image" id="file" required/> + <input type="submit" value="Upload" class="submit"/> + </div> + </form> + <p id="message"></p> + </li> + </ul> - <%-- activites --%> - <div class="col-sm-4"> - <h3>Aktivität</h3> - <%-- TODO: Retrieve achievements from database--%> - <ul class="list-group"> - <li class="list-group-item"> - <p> - Forschungsfrage erstellt - <a href="#"> - <span class="glyphicon glyphicon-link"></span> - </a> - </p> - </li> - <li class="list-group-item"> - <p> - Quiz "Goethe" erstellt - <a href="#"> - <span class="glyphicon glyphicon-link"></span> - </a> - </p> - </li> - <li class="list-group-item"> - <p> - Quiz "Schiller-Test" bearbeitet (3/5) - <a href="#"> - <span class="glyphicon glyphicon-link"></span> - </a> - </p> - </li> - <li class="list-group-item"> - <p> - Günther reviewed - <a href="#"> - <span class="glyphicon glyphicon-link"></span> - </a> - </p> - </li> - </ul> - </div> - - <%-- achievements --%> - <div class="col-sm-4"> - <h3>Erfolge</h3> - <%-- TODO: get achievements --%> + </div> - <ul class="list-group"> - <li class="list-group-item"> - Quiz "Thermodynamik" ohne Fehler absolviert - </li> - <li class="list-group-item"> - Dossier vollständig hochgeladen - </li> - </ul> - </div> - </div> + <%-- activites --%> + <div class="col-sm-4"> + <h3>Aktivität</h3> + <%-- TODO: Retrieve achievements from database--%> + <ul class="list-group"> + <li class="list-group-item"> + <p> + Forschungsfrage erstellt + <a href="#"> + <span class="glyphicon glyphicon-link"></span> + </a> + </p> + </li> + <li class="list-group-item"> + <p> + Quiz "Goethe" erstellt + <a href="#"> + <span class="glyphicon glyphicon-link"></span> + </a> + </p> + </li> + <li class="list-group-item"> + <p> + Quiz "Schiller-Test" bearbeitet (3/5) + <a href="#"> + <span class="glyphicon glyphicon-link"></span> + </a> + </p> + </li> + <li class="list-group-item"> + <p> + Günther reviewed + <a href="#"> + <span class="glyphicon glyphicon-link"></span> + </a> + </p> + </li> + </ul> </div> + <%-- achievements --%> + <div class="col-sm-4"> + <h3>Erfolge</h3> + <%-- TODO: get achievements --%> + + <ul class="list-group"> + <li class="list-group-item"> + Quiz "Thermodynamik" ohne Fehler absolviert + </li> + <li class="list-group-item"> + Dossier vollständig hochgeladen + </li> + </ul> + </div> </div> - <footer:footer/> </div> + +</div> +<footer:footer/> </body> </html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/change-phase.jsp b/gemeinsamforschen/src/main/webapp/project/change-phase.jsp index 7fd8036037002d89b9ba419861902740ce3d9b0c..f1ab43b3f5b25349d5d3abb54787568b31a9b9b9 100644 --- a/gemeinsamforschen/src/main/webapp/project/change-phase.jsp +++ b/gemeinsamforschen/src/main/webapp/project/change-phase.jsp @@ -14,32 +14,30 @@ <body> <menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <headLine:headLine/> - <div class="container-fluid"> - <input type="image" src="../libs/img/arrow.png" class="arrow" id="changePhase"/> - <div class="alert" id="CourseCreation"> - <p>Projekterstellungsphase</p> - </div> - <div class="alert" id="GroupFormation"> - <p>Gruppen erstellen</p> - </div> - <div class="alert" id="DossierFeedback"> - <p>Feedbackphase</p> - </div> - <div class="alert" id="Execution"> - <p>Durchführungsphase</p> - </div> - <div class="alert" id="Assessment"> - <p>Bewertungsphase</p> - </div> - <div class="alert" id="Projectfinished"> - <p>Ende</p> - </div> +<div class="page-content-wrapper"> + <headLine:headLine/> + <div class="container-fluid"> + <input type="image" src="../libs/img/arrow.png" class="arrow" id="changePhase"/> + <div class="alert" id="CourseCreation"> + <p>Projekterstellungsphase</p> + </div> + <div class="alert" id="GroupFormation"> + <p>Gruppen erstellen</p> + </div> + <div class="alert" id="DossierFeedback"> + <p>Feedbackphase</p> + </div> + <div class="alert" id="Execution"> + <p>Durchführungsphase</p> + </div> + <div class="alert" id="Assessment"> + <p>Bewertungsphase</p> + </div> + <div class="alert" id="Projectfinished"> + <p>Ende</p> </div> </div> - <footer:footer/> </div> +<footer:footer/> </body> </html> diff --git a/gemeinsamforschen/src/main/webapp/project/create-project.jsp b/gemeinsamforschen/src/main/webapp/project/create-project.jsp index 262280a6f9349825b160c2233e88a4fa359f59e2..35dab9be084645fb4aa1351ab131e8f330ea8087 100644 --- a/gemeinsamforschen/src/main/webapp/project/create-project.jsp +++ b/gemeinsamforschen/src/main/webapp/project/create-project.jsp @@ -9,6 +9,8 @@ <!DOCTYPE html> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> + <html> @@ -25,7 +27,6 @@ <body> <menu:menu hierarchy="1"/> -<div id="wrapper"> <div class="page-content-wrapper"> <div class="container-fluid"> <div class="row"> @@ -49,6 +50,9 @@ <div class="alert alert-danger" role="alert" style="width:475px" id="specialChars"> Der Projektname darf keine Sonderzeichen enthalten. </div> + <div class="alert alert-danger" role="alert" style="width:475px" id="projectDescriptionMissing"> + Geben Sie eine Beschreibung für Ihr Projekt an. + </div> <div class="alert alert-danger" role="alert" style="width:475px" id="projectIsMissing"> Tragen sie einen Projektnamen ein. </div> @@ -57,9 +61,6 @@ <p> Passwort zum Teilnehmen (optional) </p> <div class="form-group"><input class="form-control" name="password" placeholder="Passwort" style="width:287px;margin-left:51px;" id="passwordProject"></div> - <p> Passwort zum Löschen (sonst: 1234) </p> - <div class="form-group"><input class="form-control" name="adminpassword" placeholder="Passwort" - style="width:287px;margin-left:51px;" id="adminPassword"></div> <p>Gruppenarbeitseinstellungen</p> <input type="radio" id="lg" name="gfm" value="Basierend auf Lernzielen"> <label for="lg">Basierend auf Lernzielen</label> @@ -70,7 +71,11 @@ <input type="radio" id="single" name="gfm" value="Keine Gruppen"> <label for="single">Einzelarbeit</label> - <p>Tags </p> + <h4>Projektbeschreibung</h4> + <div> + <textarea class="" rows="4" cols="60" id="projectDescription" placeholder="meine Projektbeschreibung"></textarea> + </div> + <h4>Tags </h4> <div id="tagHelper" class="alert alert-warning" style="width:475px;"> Fügen sie zudem 5 Tags zu ihrem Projekt hinzu, welche ihr Projekt inhaltlich umreißen. </div> @@ -83,7 +88,7 @@ <button class="btn btn-primary" style="margin-left:129px;" id="sendProject">erstellen</button> </div> </div> -</div> +<footer:footer/> </body> diff --git a/gemeinsamforschen/src/main/webapp/project/css/normalize.css b/gemeinsamforschen/src/main/webapp/project/css/normalize.css index 2745b770245a42dcafe9de8d5a64c2ac044f69b0..5b1cbddfea6b166affbd52d4fe7f2ee11ca1231b 100644 --- a/gemeinsamforschen/src/main/webapp/project/css/normalize.css +++ b/gemeinsamforschen/src/main/webapp/project/css/normalize.css @@ -346,8 +346,8 @@ template { /*lists*/ ul, ul li { - list-style-type:none; - padding:0; - margin: 0; + list-style-type:none; + padding:0; + margin: 0; } diff --git a/gemeinsamforschen/src/main/webapp/project/css/style.css b/gemeinsamforschen/src/main/webapp/project/css/style.css index 5d13b8fb997c134ec18f81c2c35b7fa603f93c28..d424cfb6b2a51056a8f00191bfe377ecb15cb706 100644 --- a/gemeinsamforschen/src/main/webapp/project/css/style.css +++ b/gemeinsamforschen/src/main/webapp/project/css/style.css @@ -44,13 +44,8 @@ footer { .row { clear: both; - padding: 0px; margin: 0 auto; max-width:1280px; - - box-sizing: border-box; - - } .nav { @@ -314,7 +309,7 @@ body { } .reflection { - color:#F1D16A; + color: #F1D16A; } .presentation { @@ -337,11 +332,11 @@ body { border-left-color:#009ACC; } -.card-grouping { +.card-reflection { border-left-color:#F1D16A; } -.card-execution { +.card-presentation { border-left-color:#CCCBCB; } @@ -353,17 +348,6 @@ body { border-left-color:#F3A184; } -.card-finished{ - max-width:400px; - text-align: center; - position:relative; - background:#fff; - padding:1em; - box-sizing:border-box; - margin-bottom:1em; - -} - .inactive { color:#dedede; } @@ -376,7 +360,6 @@ header a { /*links and buttons */ button { border:none; - backrgound-color:#dedede; display:block; padding:.8em; color:#075F8B; @@ -391,3 +374,176 @@ button { display:inline; } +/* positions and text flow */ + +.centered { + text-align:center; + +} + +.centered * { + +} + +/*forms*/ + +.filter { + position: relative; + display: inline-block; + margin-bottom: 15px; + +} + +.filter select, select{ + cursor: pointer; + padding: 10px 5px; + outline: 0; + border: 2px solid #065f8b; + border-width:0 0 2px 0; + border-radius: 0px; + background: #ffffff; + color: #065f8b; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + min-width:400px; + font-size:1.5em; + line-height: 1.5em; + display:inline-block; + +} + +select::-ms-expand { + display: none; +} + +select:hover, +select:focus { + color: #009acc; + background: #ffffff; + border-color:#009acc; +} +select:disabled { + opacity: 0.5; + pointer-events: none; +} + +.select_arrow { + position: absolute; + top: 16px; + right: 18px; + width: 10px; + height: 10px; + border: solid #065f8b; + border-width: 0 2px 2px 0; + display: inline-block; + padding: 3px; + transform: rotate(45deg); + -webkit-transform: rotate(45deg); +} + +select:hover ~ .select_arrow, +select:focus ~ .select_arrow { + border-color: #009acc; +} +select:disabled ~ .select_arrow { + border-top-color: #cccccc; +} +.search { + display:inline-block; + position:relative; +} + +.search input { + cursor:text; + padding: 10px 5px; + outline: 0; + border: 2px solid #065f8b; + border-width:0 0 2px 0; + border-radius: 0px; + background: #ffffff; + color: #065f8b; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + min-width:400px; + font-size:1.5em; + box-sizing: border-box; + line-height: 1.5em; +} + +.search input:hover { + color: #009acc; + background: #ffffff; + border-color:#009acc; +} + +.search input:hover .fa-search { + color: #009acc; +} + +.search .fa-search { + position: absolute; + bottom:5px; + right:10px; + font-size:1.5em; + line-height: 1.5em; + padding: 8px 0px 2px 0px; + color:#065f8b; + +} + + +/* pages */ + +.projects .introduction { + max-width:400px; + margin: 0 auto; + + margin-bottom: 0px; + + margin-bottom: 4em; +} + +.projects h1 { + text-align:center; + max-width:100%; +} + + +.projects-grid { + display: flex; + flex-flow: row wrap; + justify-content: flex-start; + + +} +.projects .card { + /*width:200px; + height:200px;*/ + + margin-bottom:0; + flex:1 1 200px; + border-left:0; + height: 200px; + width:200px; + padding: 0; + + -webkit-box-shadow: 0px 0px 0px -8px rgba(140,138,140,0.62); + -moz-box-shadow: 0px 0px 0px -8px rgba(140,138,140,0.62); + box-shadow: 0px 0px 0px -8px rgba(140,138,140,0.62); + +} + +.projects .card div { + + margin:0px 5px 0px 5px; + + /* border-top:10px solid #065f8b;*/ + border-top:10px solid #065f8b; + padding:5px; + position:relative; + -webkit-box-shadow: 0px 3px 5px 0px rgba(140,138,140,0.62); + -moz-box-shadow: 0px 3px 5px 0px rgba(140,138,140,0.62); + box-shadow: 0px 3px 5px 0px rgba(140,138,140,0.62); +} \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/delete-project.jsp b/gemeinsamforschen/src/main/webapp/project/delete-project.jsp index 330fc8e42f0de1853034636f99c7b7997d3a777d..693eadaa1138f628dd4f83aa206e569aa3ed18d1 100644 --- a/gemeinsamforschen/src/main/webapp/project/delete-project.jsp +++ b/gemeinsamforschen/src/main/webapp/project/delete-project.jsp @@ -22,39 +22,16 @@ <body> <div class="loader-inactive" id="loader"></div> <menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <div class="container-fluid"><a class="btn btn-link" role="button" href="#menu-toggle" id="menu-toggle"></a> - <div class="row"> - <div class="col-md-12"> - <h3>Projekt löschen! </h3> - <div class="page-header"></div> - </div> - </div> - </div> - </div> - <fieldset> - <legend style="margin-left:13px;">Projektnamen</legend> - <input class="form-control" type="text" id="projectName" name="Project" required="" - placeholder="Projekt1" autofocus="" - style="max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> - <div class="alert alert-warning" role="alert" id="projectIsMissing"> - Dieser Projektname existiert nicht oder das Passwort ist falsch. - </div> - - </fieldset> - <fieldset> - <legend style="margin-left:13px;">Passwort zum löschen</legend> - <input class="form-control" type="password" id="projectPassword" name="Password" required="" - placeholder="******" - style="max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> - <div class="alert alert-warning" role="alert" id="projectWrongPassword"> - Falsches Passwort. - </div> - </fieldset> - <button id="deleteProject" class="btn btn-danger">löschen</button> - <footer:footer/> +<headLine:headLine/> +<legend style="margin-left:13px;">Projektnamen</legend> +<input class="form-control" type="text" id="projectName" name="Project" required + placeholder="Projekt1" autofocus + style="max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> +<div class="alert alert-warning" role="alert" id="projectIsMissing"> + Dieser Projektname existiert nicht </div> +<button id="deleteProject" class="btn btn-danger">löschen</button> +<footer:footer/> </body> </html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/edit-assessment-settings.jsp b/gemeinsamforschen/src/main/webapp/project/edit-assessment-settings.jsp index 536f3138638e9d90e372f477199b365349e94e4c..8059df80bd550edd6c6732d2341abbac131437b4 100644 --- a/gemeinsamforschen/src/main/webapp/project/edit-assessment-settings.jsp +++ b/gemeinsamforschen/src/main/webapp/project/edit-assessment-settings.jsp @@ -1,5 +1,7 @@ <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> + <!DOCTYPE html> <html> @@ -10,14 +12,11 @@ <body> <menu:menu hierarchy="1"/> -<div id="wrapper"> <h1> Bewertungsverfahren einstellen </h1> -<a - href="edit-group-settings.jsp"> - Weiter </a> +<a href="edit-group-settings.jsp">Weiter </a> -</div> +<footer:footer/> </body> </html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/enroll-project.jsp b/gemeinsamforschen/src/main/webapp/project/enroll-project.jsp deleted file mode 100644 index 08126dcd2b321427617c0df77cb83cd0dedaae87..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/webapp/project/enroll-project.jsp +++ /dev/null @@ -1,56 +0,0 @@ -<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> - -<!DOCTYPE html> -<html> - -<head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Gruppenmatcher</title> - <script src="../groupfinding/js/config.js"></script> - <script src="../taglibs/js/utility.js"></script> - <script src="js/join-project.js"></script> - -</head> - -<body> -<menu:menu hierarchy="1"/> -<div class="loader-inactive" id="loader"></div> -<div id="wrapper" class="wrapper"> - <div class="page-content-wrapper"> - <div class="container-fluid"><a class="btn btn-link" role="button" href="#menu-toggle" id="menu-toggle"></a> - <div class="row"> - <div class="col-md-12"> - <h3>Tragen sie sich in ein neues Projekt ein. </h3> - <div class="page-header"></div> - </div> - </div> - </div> - </div> - <fieldset> - <legend style="margin-left:13px;">Projektnamen</legend> - <input class="form-control" type="text" id="projectName" name="Project" required="" - placeholder="Projekt1" autofocus="" - style="max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> - <div class="alert alert-warning" role="alert" id="projectIsMissing"> - Dieser Projektname existiert nicht. - </div> - - </fieldset> - <fieldset> - <legend style="margin-left:13px;">Passwort</legend> - <input class="form-control" type="password" id="projectPassword" name="Password" required="" - placeholder="******" - style="max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> - <div class="alert alert-warning" role="alert" id="projectWrongPassword"> - Falsches Passwort. - </div> - </fieldset> - <button id="seeProject" class="btn btn-primary">Einsehen</button> -</div> -</body> - -</html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/join-project.jsp b/gemeinsamforschen/src/main/webapp/project/join-project.jsp deleted file mode 100644 index 66ba1f3bbc5c0830d76ff7bd66da7c6fe6f05f96..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/webapp/project/join-project.jsp +++ /dev/null @@ -1,46 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> -<!DOCTYPE html> -<html> - -<head> - <omniDependencies:omniDependencies hierarchy="1"/> - <script src="../groupfinding/js/config.js"></script> - <script src="js/join-project.js"></script> - - -</head> - -<body> -<menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <headLine:headLine/> - <fieldset> - <legend style="margin-left:13px;">Projektnamen</legend> - <input class="form-control" type="text" id="projectName" name="Project" required="" - placeholder="Projekt1" autofocus="" - style="max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> - <div class="alert alert-warning" role="alert" id="projectIsMissing"> - Dieser Projektname existiert nicht. - </div> - - </fieldset> - <fieldset> - <legend style="margin-left:13px;">Passwort</legend> - <input class="form-control" type="password" id="projectPassword" name="Password" required="" - placeholder="******" - style="max-width:417px;margin-left:14px;padding-top:10px;margin-top:2px;margin-bottom:13px;"> - <div class="alert alert-warning" role="alert" id="projectWrongPassword"> - Falsches Passwort. - </div> - </fieldset> - <button id="loginProject" class="btn btn-primary">Einsehen</button> - </div> -</div> -</body> - -</html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/js/create-project.js b/gemeinsamforschen/src/main/webapp/project/js/create-project.js index f6dfd32d0408281b3abb07b931fd137afff8fae6..baa1e6c5eba444589b1ffad2b5f384742145a3ae 100644 --- a/gemeinsamforschen/src/main/webapp/project/js/create-project.js +++ b/gemeinsamforschen/src/main/webapp/project/js/create-project.js @@ -1,5 +1,6 @@ let allTheTags = []; let projectName = ""; +var gfm = ""; /** * Created by fides-WHK on 19.02.2018. @@ -19,25 +20,26 @@ function createNewProject(allTheTags, activ) { errorMessages(); // getting the data from the form fields let project = getProjectValues(); + let projectName = $("#nameProject").val().trim(); // create the project if (project) { // create the project in local db - let localurl = "../../gemeinsamforschen/rest/project/create"; + let localurl = "../rest/project/create"; $.ajax({ //check local DB for existence of projectName url: localurl, + projectName: projectName, contentType: 'application/json', activ: activ, - type: 'PUT', + type: 'POST', data: JSON.stringify(project), success: function (response) { - if (response === "project exists") { + if (response === "Project already exists") { $('#projectNameExists').show(); } else { if (allTheTags.length !== 5) { $('#exactNumberOfTags').show(); } else { - createProjectinCompbase(); - + sendGroupPreferences(); } } }, @@ -55,6 +57,7 @@ function errorMessages() { $('#projectIsMissing').hide(); $('#exactNumberOfTags').hide(); $('#specialChars').hide(); + $('#projectDescriptionMissing').hide(); document.getElementById('tagHelper').className = ""; } @@ -83,13 +86,9 @@ function initSendButton(allTheTags) { function getProjectValues() { projectName = $("#nameProject").val().trim(); let password = $("#passwordProject").val().trim(); - let adminPassword = $("#adminPassword").val().trim(); - if (adminPassword === "") { - adminPassword = "1234"; - } //allTheTags = $("#tagsProject").tagsInput('items'); //allTheTags = $("#tagsProject").val(); - let reguexp = /^[a-zA-Z0-9äüöÄÜÖ\ ]+$/; + let reguexp = /^[a-zA-Z0-9äüöÄÜÖ]+$/; if (!reguexp.test(projectName)) { $('#specialChars').show(); return false; @@ -98,37 +97,28 @@ function getProjectValues() { $('#projectIsMissing').show(); return false; } - + let description = $('#projectDescription').val(); + if (description === ""){ + $('#projectDescriptionMissing').show(); + return false; + } if (allTheTags.length !== 5) { document.getElementById('tagHelper').className = "alert alert-warning"; } else { document.getElementById('tagHelper').className = ""; } + let time = new Date().getTime(); - // TODO find out author - let project = { + return { "name" : projectName, "password" : password, + "description": description, "active" : true, - "timecreated" : 356122661234038, - "authorEmail": "vodka", - "adminPassword": adminPassword, - "phase" : "CourseCreation", + "phase" : "GroupFormation", + "timecreated" : time, + "authorEmail": $('#userEmail').text().trim(), "tags": allTheTags }; - - /* let project = { - "name" : "AJ2c83Lb2x", - "password" : "vTvaih3mK9", - "active" : true, - "timecreated" : 356122661234038, - "authorEmail" : "7DoIYf4tWV", - "adminPassword" : "bJFmgTGMdY", - "phase" : "Execution", - "tags" : [ "JjwWui3r2a", "J23BLwqlXa", "NOVk1tcaN0", "RTXTACSHLx", "BbMtdrXPi2" ] - }; -*/ - return project; } // creates project name in compbase where it is needed for learning goal oriented matching @@ -141,7 +131,7 @@ function createProjectinCompbase() { "competences": allTheTags }; let dataString = JSON.stringify(obj); - let addProjectNeo4j = $.ajax({ + $.ajax({ url: url, contentType: 'application/json', activ: true, @@ -150,12 +140,46 @@ function createProjectinCompbase() { success: function (response) { console.log(response); // it actually worked, too - sendGroupPreferences(); + document.location.href = "tasks-docent.jsp?projectName="+projectName; }, - error: function (a, b, c) { + error: function (a) { console.log(a); // and also in this case return false; } }); } + + +function sendGroupPreferences() { + gfm = $('input[name=gfm]:checked').val(); + if (gfm == "Basierend auf Präferenzen") { + // TODO implement preference based group selection + gfm = "UserProfilStrategy"; + } else if (gfm == "per Hand") { + gfm = "Manual"; + } else if (gfm == "Basierend auf Lernzielen") { + gfm = "LearningGoalStrategy"; + } else if(gfm == "Keine Gruppen") { + gfm = "SingleUser"; + } + + var localurl = "../../gemeinsamforschen/rest/group/gfm/create/projects/" + projectName; + $.ajax({ + gfm: gfm, + url: localurl, + contentType: 'application/json', + type: 'POST', + data: gfm, + success: function (a, b, c) { + if (gfm == "LearningGoalStrategy") { + createProjectinCompbase(); + } + document.location.href = "tasks-docent.jsp?projectName="+projectName; + return true; + }, + error: function (a, b, c) { + return false; + } + }); +} diff --git a/gemeinsamforschen/src/main/webapp/project/js/deleteProject.js b/gemeinsamforschen/src/main/webapp/project/js/deleteProject.js index 43a3e445f9316e2f32ef31707fbd94ad1825651b..5bfd989135cf427c5967e2933ab49152e27bb36d 100644 --- a/gemeinsamforschen/src/main/webapp/project/js/deleteProject.js +++ b/gemeinsamforschen/src/main/webapp/project/js/deleteProject.js @@ -6,52 +6,45 @@ $(document).ready(function () { $("#projectIsMissing").hide(); $("#deleteProject").on('click', function () { - deleteProject($('#projectName').val().trim()); + let projectName = $('#projectName').text().trim(); + deleteProject(projectName); }); }); function deleteProject(projectName) { - var token = getUserEmail(); - var url = "../database/delete-project.jsp?project=" + projectName + "&password=" + document.getElementById('projectPassword').value.trim(); + let localurl = "../rest/project/delete/project/" + projectName; if (projectName === "") { return false; } else { $.ajax({ - url: url, + url: localurl, + data: "", projectName: projectName, - Accept: "text/plain; charset=utf-8", contentType: "text/plain", + type: 'POST', success: function (response) { if (response === "project missing") { $("#projectIsMissing").show(); } else { - if (response !== "wrong password") { //if response !== project missing and not wrong password, its the projectName - $.ajax({ - url: compbaseUrl + "/api1/courses/" + projectName, - Accept: "text/plain; charset=utf-8", - type: 'DELETE', - contentType: "text/plain", - async: false, - success: function (response) { + $.ajax({ + url: compbaseUrl + "/api1/courses/" + projectName, + Accept: "text/plain; charset=utf-8", + type: 'DELETE', + contentType: "text/plain", + async: false, + success: function (response) { - }, - error: function (a, b, c) { - console.log(a); - } - }); - window.location.href = " ../pages/projects.php" + getUserEmail(); - } else { - $("#projectIsMissing").hide(); - $('#projectWrongPassword').show(); - } + }, + error: function (a) { + console.log(a); + } + }); + window.location.href = " ../projects/overview-docent.jsp"; } }, - error: function (a, b, c) { + error: function (a) { console.log(a); } }); - - } - } diff --git a/gemeinsamforschen/src/main/webapp/project/js/edit-group-settings.js b/gemeinsamforschen/src/main/webapp/project/js/edit-group-settings.js index 4c6e462ceacd859d81637748073d01101fa10603..8b137891791fe96927ad78e64b0aad7bded08bdc 100644 --- a/gemeinsamforschen/src/main/webapp/project/js/edit-group-settings.js +++ b/gemeinsamforschen/src/main/webapp/project/js/edit-group-settings.js @@ -1,41 +1 @@ -var gfm = ""; - -function sendGroupPreferences() { - gfm = $('input[name=gfm]:checked').val(); - if (gfm == "Basierend auf Präferenzen") { - // TODO implement preference based group selection - gfm = "UserProfilStrategy"; - } else if (gfm == "per Hand") { - gfm = "Manual"; - } else if (gfm == "Basierend auf Lernzielen") { - gfm = "LearningGoalStrategy"; - } else if(gfm == "Keine Gruppen") { - gfm = "SingleUser"; - } - - var localurl = "../../gemeinsamforschen/rest/group/gfm/create/projects/" + projectName; - $.ajax({ - gfm: gfm, - url: localurl, - contentType: 'application/json', - type: 'POST', - data: gfm, - success: function (a, b, c) { - /* if (gfm == "Manual") { - document.location.href = "../groupfinding/create-groups-manual.jsp" + "&projectName=" + projectToken; - } - if (gfm == "UserProfilStrategy") { - document.location.href = "../groupfinding/create-groups-preferences.jsp" + "&projectName=" + projectToken; - } - else { - document.location.href = "../groupfinding/create-groups-learninggoal.js.jsp" + "&projectName=" + projectToken; - }*/ - document.location.href = "tasks-docent.jsp?projectName="+projectName; - return true; - }, - error: function (a, b, c) { - return false; - } - }); -} \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/js/join-project.js b/gemeinsamforschen/src/main/webapp/project/js/join-project.js deleted file mode 100644 index 1f2b0469da32c12c2ff418a8c4ac14a420cc67a9..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/webapp/project/js/join-project.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Created by fides-WHK on 09.01.2018. - */ - -var projectName = ""; - -$(document).ready(function () { - $("#projectWrongPassword").hide(); - $("#projectIsMissing").hide(); - - $("#projectName").keypress(function (e) { - if (e.which == 13) { - document.getElementById("projectPassword").focus(); - } - }); - $("#projectPassword").keypress(function (e) { - if (e.which == 13) { - loginProject($('#projectName').val()); - } - }); - $("#loginProject").on('click', function () { - projectName = $('#projectName').val(); - loginProject(); - }); -}); - -function loginProject() { - var password = $('#projectPassword').val(); - var url = "../../gemeinsamforschen/rest/project/login/"+projectName+"?password="+password; - if (projectName === "") { - return false; - } else { - $.ajax({ - url: url, - projectName: projectName, - Accept: "text/plain; charset=utf-8", - contentType: "text/plain", - success: function (response) { - if (response === "project missing") { - $("#projectIsMissing").show(); - } else { - if (response !== "wrong password") { //if response !== project missing and not wrong password, its the projectName - var projectToken = response; - document.location.href = "../groupfinding/enter-preferences.jsp?projectName="+projectName; - } else { - $("#projectIsMissing").hide(); - $('#projectWrongPassword').show(); - } - } - }, - error: function (a, b, c) { - console.log(a); - $("#projectIsMissing").show(); - } - }); - } -} \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/js/myCourses-student.js b/gemeinsamforschen/src/main/webapp/project/js/myCourses-student.js new file mode 100644 index 0000000000000000000000000000000000000000..c7bbecd3e3379a0423355851877ba277dccdcc24 --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/project/js/myCourses-student.js @@ -0,0 +1,107 @@ +$(document).ready(function () { + let userName = $('#userEmail').html().trim(); + getProjects(userName); + $('#enrollProject').on('click', function () { + location.href = "join-project.jsp"; + }); +}); + +function updateStatus(projectName) { + $.ajax({ + url: '../rest/phases/projects/' + projectName, + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache" + }, + type: 'GET', + success: function (response) { + let statusField = $('#status' + projectName); + switch (response) { + case "CourseCreation": + statusField.html("Der Kurs wurde gerade angelegt. Sie können sich nun anmelden."); + break; + case "GroupFormation": + statusField.html("Ihr Dozent ordnet Sie nun einer Gruppe zu."); + break; + case "DossierFeedback": + statusField.html("Geben sie wenigstens einem Gruppenmitglied Feedback und erstellen sie ein Dossier in Ihrer Gruppe."); + break; + case "Execution": + statusField.html("Forschen Sie zu Ihrer Forschungsfrage und reflektieren Sie ihr Vorgehen mit dem Journal"); + break; + case "Assessment": + statusField.html("Nehmen Sie die Bewertungen vor."); + break; + case "Projectfinished": + getGrade(projectName); + break; + default: + break; + } + + }, + error: function (a) { + + } + }); +} + +function getGrade(projectName) { + let userName = $('#userEmail').html().trim(); + $.ajax({ + url: '../rest/assessments/get/project/' + projectName + '/student/' + userName, + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache" + }, + type: 'GET', + success: function (response) { + $('#status_' + projectName).html("Sie erreichten " + response + "%"); + }, + error: function (a) { + } + }); +} + +function getProjects(userName) { + $.ajax({ + url: '../rest/project/all/student/' + userName, + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache" + }, + type: 'GET', + success: function (response) { + let tmplObject = []; + for (let project in response) { + if (response.hasOwnProperty(project)) + if (response[project].active) { + let projectDescription = "Der Kurs wurde beschrieben mit \""+ + response[project].tags[0] + "\", \"" + + response[project].tags[1] + "\", \" " + + response[project].tags[2] + "\", \" " + + response[project].tags[3] + "\" und \" " + + response[project].tags[4]+ "\""; + tmplObject.push({ + projectName: response[project].name, + projectAuthor: response[project].authorEmail, + projectDescription: projectDescription + }); + } + } + + $('#projectTemplate').tmpl(tmplObject).appendTo('#projects'); + for (let project in response) { + if (response.hasOwnProperty(project)) { + $('#project_' + response[project].name).on('click', function () { + location.href = "tasks-student.jsp?projectName=" + response[project].name; + }); + updateStatus(response[project].name); + } + } + }, + error: function (a) { + + } + }); +} \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/js/overview-student.js b/gemeinsamforschen/src/main/webapp/project/js/search-project.js similarity index 56% rename from gemeinsamforschen/src/main/webapp/project/js/overview-student.js rename to gemeinsamforschen/src/main/webapp/project/js/search-project.js index dded271a32edc34f4521c5a66188892fca460862..d46fee39ff81504f8df26aafe1e0be4fed09a516 100644 --- a/gemeinsamforschen/src/main/webapp/project/js/overview-student.js +++ b/gemeinsamforschen/src/main/webapp/project/js/search-project.js @@ -1,22 +1,23 @@ -$(document).ready(function(){ +$(document).ready(function () { let userName = $('#userEmail').html().trim(); getProjects(userName); - $('#enrollProject').on('click', function(){ - location.href="join-project.jsp"; + $('#enrollProject').on('click', function () { + location.href = "join-project.jsp"; }); + }); -function updateStatus(projectName){ +function updateStatus(projectName) { $.ajax({ - url: '../rest/phases/projects/'+projectName, + url: '../rest/phases/projects/' + projectName, headers: { "Content-Type": "application/json", "Cache-Control": "no-cache" }, type: 'GET', success: function (response) { - let statusField = $('#status'+projectName); - switch (response){ + let statusField = $('#status' + projectName); + switch (response) { case "CourseCreation": statusField.html("Der Kurs wurde gerade angelegt. Sie können sich nun anmelden."); break; @@ -46,49 +47,55 @@ function updateStatus(projectName){ }); } -function getGrade(projectName){ +function getGrade(projectName) { let userName = $('#userEmail').html().trim(); $.ajax({ - url: '../rest/assessments/get/project/'+projectName+'/student/'+userName, + url: '../rest/assessments/get/project/' + projectName + '/student/' + userName, headers: { "Content-Type": "application/json", "Cache-Control": "no-cache" }, type: 'GET', success: function (response) { - $('#status_'+projectName).html("Sie erreichten "+response+"%"); + $('#status_' + projectName).html("Sie erreichten " + response + "%"); }, - error: function(a){ + error: function (a) { } }); } -function getProjects(userName){ +function getProjects() { $.ajax({ - url: '../rest/project/all/student/' + userName, + url: '../rest/project/all', headers: { - "Content-Type": "text/plain", + "Content-Type": "application/json", "Cache-Control": "no-cache" }, type: 'GET', success: function (response) { let tmplObject = []; - for (let project in response){ + for (let project in response) { if (response.hasOwnProperty(project)) - tmplObject.push({projectName: response[project]}); + if (response[project].active) { + tmplObject.push({ + projectName: response[project].name, + projectAuthor: response[project].authorEmail, + projectDescription: response[project].description + }); + } } - $('#projectTRTemplate').tmpl(tmplObject).appendTo('#projects'); - for (let projectName in response){ - if (response.hasOwnProperty(projectName)) { - $('#project_' + response[projectName]).on('click', function () { - location.href="tasks-student.jsp?projectName="+response[projectName]; - }); - updateStatus(response[projectName]); + $('#projectTemplate').tmpl(tmplObject).appendTo('#projects'); + for (let project in response) { + if (response.hasOwnProperty(project)) { + $('#project_' + response[project].name).on('click', function () { + location.href = "../groupfinding/enter-preferences.jsp?projectName=" + response[project].name; + }); + updateStatus(response[project].name); } } }, - error: function(a){ + error: function (a) { } }); diff --git a/gemeinsamforschen/src/main/webapp/project/js/tasks.js b/gemeinsamforschen/src/main/webapp/project/js/tasks.js index 1dd834e6640f3779256210de5c0bca9bc0cd11b0..220e5bda91bb934a586b5901b47a4b11117397c4 100644 --- a/gemeinsamforschen/src/main/webapp/project/js/tasks.js +++ b/gemeinsamforschen/src/main/webapp/project/js/tasks.js @@ -1,15 +1,15 @@ /** * TODO @Axel use rendertemplate for participantcount and link manual groups creation as option */ -$(document).ready(function(){ +$(document).ready(function () { let userEmail = $('#userEmail').html().trim(); let projectName = $('#projectName').html().trim(); fillTasks(projectName, userEmail); }); -function fillTasks(projectName, userEmail){ +function fillTasks(projectName, userEmail) { $.ajax({ - url: '../rest/tasks/user/'+encodeURI(userEmail)+'/project/' + projectName, + url: '../rest/tasks/user/' + encodeURI(userEmail) + '/project/' + projectName, headers: { "Content-Type": "application/json", "Cache-Control": "no-cache" @@ -17,18 +17,23 @@ function fillTasks(projectName, userEmail){ type: 'GET', success: function (response) { let object = fillObjectWithTasks(response); - for (let task in object){ + for (let task in object) { let tmplObject = fitObjectInTmpl(object[task]); - $('#taskTemplate').tmpl(tmplObject).appendTo('#listOfTasks'); + if (tmplObject.taskProgress === "FINISHED") { + $('#finishedTaskTemplate').tmpl(tmplObject).appendTo('#listOfTasks'); + } else { + $('#taskTemplate').tmpl(tmplObject).appendTo('#listOfTasks'); + } } }, - error: function(a){} + error: function (a) { + } }); } -function fitObjectInTmpl(object){ +function fitObjectInTmpl(object) { let result = { taskType: "", infoText: "", @@ -41,135 +46,182 @@ function fitObjectInTmpl(object){ }; - if (object.progress==="FINISHED"){ - if(object.taskName.includes("CLOSE")){ - result.taskProgress="FINISHED"; - result.infoText=object.phase; - let created = new Date(object.eventCreated); - let deadline=new Date(object.deadline); - result.timeFrame="<p>"+created.getDate()+"."+(created.getMonth()+1)+"."+created.getFullYear() + - " bis "+deadline.getDate()+"."+(deadline.getMonth()+1)+"."+deadline.getFullYear()+"</p>"; - return result; - } - } - - - if (object.taskType!=="INFO"){ - if (object.groupTask===true){ - result.taskType="grouptask" - }else{ - result.taskType="usertask" + if (object.taskType !== "INFO") { + if (object.groupTask === true) { + result.taskType = "grouptask" + } else { + result.taskType = "usertask" } - }else{ - result.taskType="infotask" + } else { + result.taskType = "infotask" } - switch (object.phase){ + switch (object.phase) { case "CourseCreation": - result.phase="card-draft"; + result.phase = "card-draft"; break; case "GroupFormation": - result.phase="card-grouping"; + result.phase = "card-grouping"; break; case "DossierFeedback": - result.phase="card-feedback"; + result.phase = "card-feedback"; break; case "Execution": - result.phase="card-execution"; + result.phase = "card-execution"; break; case "Assessment": - result.phase="card-assessment"; + result.phase = "card-assessment"; break; case "Projectfinished": - result.phase="card-grades"; + result.phase = "card-grades"; break; default: - result.phase=""; + result.phase = ""; } - if (object.deadline != null){ - let daysLeft = Math.round((object.deadline - Date.now())/1000/60/60/24); - if (daysLeft>=1) - result.timeFrame="<div class='status icon'><p>Noch "+daysLeft+" Tage Zeit</p></div>"; + if (object.deadline != null) { + let daysLeft = Math.round((object.deadline - Date.now()) / 1000 / 60 / 60 / 24); + if (daysLeft >= 1) + result.timeFrame = "<div class='status icon'><p>Noch " + daysLeft + " Tage Zeit</p></div>"; else - result.timeFrame="<div class='status alert icon'><p>Du bist zu spät.</p></div>"; - }else {result.timeFrame="";} - switch (object.taskName){ + result.timeFrame = "<div class='status alert icon'><p>Du bist zu spät.</p></div>"; + } else { + result.timeFrame = ""; + } + switch (object.taskName) { case "WAIT_FOR_PARTICPANTS": - result.infoText = "Warten Sie auf die Anmeldungen der Studenten."; + result.infoText = "Warten Sie auf die Anmeldungen der Studenten.\n" + + "Es sind bereits "+object.taskData.participantCount.participants+" Studenten eingetragen."; break; case "BUILD_GROUPS": - result.infoText="Erstellen Sie die Gruppen."; + result.infoText = "Erstellen Sie die Gruppen."; break; case "CLOSE_GROUP_FINDING_PHASE": - result.infoText="Gehen Sie zur nächsten Phase über."; + result.infoText = "Gehen Sie zur nächsten Phase über."; break; case "WAITING_FOR_GROUP": - result.infoText="[STUDENT] Die Arbeitsgruppen werden gebildet. Sie werden informiert, wenn es so weit" + + result.infoText = "[STUDENT] Die Arbeitsgruppen werden gebildet. Sie werden informiert, wenn es so weit" + " ist."; break; - + case "UPLOAD_DOSSIER": + result.infoText = "Legen Sie ein Dossier an."; + break; + case "ANNOTATE_DOSSIER": + result.infoText = "Annotieren Sie ihr Dossier."; + break; case "GIVE_FEEDBACK": - result.infoText="[STUDENT] Geben Sie ein Feedback ....."; + result.infoText = "[STUDENT] Geben Sie ein Feedback ....."; + break; + case "WAITING_FOR_STUDENT_DOSSIERS": + result.infoText = "[TEACHER] Warten Sie darauf, dass jeder Student ein Dossier" + + "hochlädt und ein Feedback für jemanden gab."; + break; + case "CLOSE_DOSSIER_FEEDBACK_PHASE": + let count = object.taskData.length; + if (count<=3){ + result.infoText="Warten sie noch auf die Studenten "; + for (let i=0; i<object.taskData.length; i++){ + result.infoText += object.taskData[i]+" "; + } + }else{ + result.infoText="Noch haben nicht alle Studenten ihren Peers ein Feedback gegeben."; + } + break; + case "WAIT_FOR_REFLECTION": + result.infoText = "[TEACHER] Nun haben die Studenten Zeit über sich und die Welt nachzudenken."; break; case "EDIT_FORMED_GROUPS": result.infoText = "[TEACHER] Die Gruppen wurden vom Algorithmus gebildet. Sie können noch manuell" + " editiert werden."; // hier müsste noch ein Link eingefügt werden, zur manuellen Gruppenbildung break; + case "CONTACT_GROUP_MEMBERS": + result.infoText = "Sagen sie hallo zu ihren Gruppenmitgliedern über den Chat."; + break; default: - result.infoText=""; + result.infoText = ""; } - if (object.taskType.includes("LINKED")){ + if (object.taskType.includes("LINKED")) { switch (object.taskName) { + case "WAIT_FOR_PARTICPANTS": + if (object.taskData.gfm==="Manual"){ + result.solveTaskWith="Gruppen erstellen"; + result.solveTaskWithLink="redirect(\'../groupfinding/create-groups-manual.jsp?projectName="+object.projectName+"\')"; + } + break; case "CLOSE_GROUP_FINDING_PHASE": - result.solveTaskWith="Entwurfsphase starten"; - result.solveTaskWithLink="closePhase(\'"+object.phase+"\', \'"+object.projectName+"\');"; + result.solveTaskWith = "Entwurfsphase starten"; + result.solveTaskWithLink = "closePhase(\'" + object.phase + "\', \'" + object.projectName + "\');"; break; case "UPLOAD_DOSSIER": - result.solveTaskWith="Lege ein Dossier an"; - result.solveTaskWithLink="redirect(\'../annotation/upload-unstructured-dossier.jsp?projectName="+object.projectName+"\')"; + result.solveTaskWith = "Lege ein Dossier an"; + result.solveTaskWithLink = "redirect(\'../annotation/upload-unstructured-dossier.jsp?projectName=" + object.projectName + "\')"; break; - /* case "GIVE_FEEDBACK": - result.solveTaskWith="Erteile Feedback"; - result.solveTaskWithLink="redirect(\'../feedback/give-feedback.jsp?projectName="+object.projectName+"\')"; - break;*/ case "CREATE_QUIZ": - result.solveTaskWith="Erstelle ein Quiz"; - result.solveTaskWithLink="redirect(\'../assessment/create-quiz.jsp?projectName="+object.projectName+"\')"; + result.solveTaskWith = "Erstelle ein Quiz"; + result.solveTaskWithLink = "redirect(\'../assessment/create-quiz.jsp?projectName=" + object.projectName + "\')"; break; case "WRITE_EJOURNAL": - result.solveTaskWith="Lege ein EJournal an"; - result.solveTaskWithLink="redirect(\'../journal/create-journal.jsp?projectName="+object.projectName+"\')"; + result.solveTaskWith = "Lege ein EJournal an"; + result.solveTaskWithLink = "redirect(\'../journal/create-journal.jsp?projectName=" + object.projectName + "\')"; break; case "ANNOTATE_DOSSIER": - result.solveTaskWith="Annotiere das Dossier"; - result.solveTaskWithLink="redirect(\'../annotation/create-unstructured-annotation.jsp?projectName="+object.projectName+"&submissionId=" + object.taskData.fullSubmissionId+"\')"; + result.solveTaskWith = "Annotiere das Dossier"; + result.solveTaskWithLink = "redirect(\'../annotation/create-unstructured-annotation.jsp?projectName=" + object.projectName + "&submissionId=" + object.taskData.fullSubmissionId + "\')"; break; case "FINALIZE_DOSSIER": - result.solveTaskWith="Finalisiere das Dossier"; - result.solveTaskWithLink="redirect(\'../annotation/create-unstructured-annotation.jsp?projectName="+object.projectName+"&submissionId=" + object.taskData.fullSubmissionId+"\')"; + result.solveTaskWith = "Finalisiere das Dossier"; + result.solveTaskWithLink = "redirect(\'../annotation/create-unstructured-annotation.jsp?projectName=" + object.projectName + "&submissionId=" + object.taskData.fullSubmissionId + "\')"; break; case "FINALIZE_EJOURNAL": - result.solveTaskWith="Finalisiere dein EJournal"; - result.solveTaskWithLink="redirect(\'../journal/edit-description.jsp?projectName="+object.projectName+"\')"; + result.solveTaskWith = "Finalisiere dein EJournal"; + result.solveTaskWithLink = "redirect(\'../journal/edit-description.jsp?projectName=" + object.projectName + "\')"; + break; + case "CLOSE_DOSSIER_FEEDBACK_PHASE": + result.taskData = object.taskData; + if (result.taskData.length === 0) { + result.solveTaskWith = "Reflexionsphase starten"; + result.solveTaskWithLink = "closePhase(\'" + object.phase + "\', \'" + object.projectName + "\');"; + } else { + if (result.taskData.length <= 3) { //todo: probably its better to have a percentage of all participants as constraint + + } + } + break; case "ASSESSMENT": - result.solveTaskWith="Starte Bewertung"; - result.solveTaskWithLink="redirect(\'../assessment/assess-work.jsp?projectName="+object.projectName+"\')"; + result.solveTaskWith = "Starte Bewertung"; + result.solveTaskWithLink = "redirect(\'../assessment/assess-work.jsp?projectName=" + object.projectName + "\')"; break; case "GIVE_FEEDBACK": - result.solveTaskWith="Geben Sie ein Feedback"; - result.solveTaskWithLink="redirect(\'../annotation/annotation-document.jsp?fullSubmissionId="+object.taskData.fullSubmission.id+"&category="+object.taskData.category+"\')"; + result.solveTaskWith = "Geben Sie ein Feedback"; + result.solveTaskWithLink = "redirect(\'../annotation/annotation-document.jsp?" + + "projectName=" + object.projectName + + "&fullSubmissionId=" + object.taskData.fullSubmission.id + "&category=" + object.taskData.category + "\')"; break; default: - result.solveTaskWith=null; + result.solveTaskWith = null; } } + + if (object.progress === "FINISHED") { + if (object.taskName.includes("CLOSE")) { + result.taskProgress = "FINISHED"; + result.infoText = object.phase; + let created = new Date(object.eventCreated); + let deadline = new Date(object.deadline); + result.timeFrame = "<p>" + created.getDate() + "." + (created.getMonth() + 1) + "." + created.getFullYear() + + " bis " + deadline.getDate() + "." + (deadline.getMonth() + 1) + "." + deadline.getFullYear() + "</p>"; + return result; + } + result.taskProgress = "FINISHED"; + return result; + } + return result; } -function fillObjectWithTasks(response){ - let tempObject=[]; - for (let task in response){ +function fillObjectWithTasks(response) { + let tempObject = []; + for (let task in response) { if (response.hasOwnProperty(task)) tempObject.push({ taskType: response[task].taskType, // @@ -187,15 +239,16 @@ function fillObjectWithTasks(response){ progress: response[task].progress }); } + return tempObject; } -function redirect(url){ - location.href=url; +function redirect(url) { + location.href = url; } -function closePhase(phase, projectName){ - let innerurl = '../rest/phases/'+phase+'/projects/'+projectName+'/end'; +function closePhase(phase, projectName) { + let innerurl = '../rest/phases/' + phase + '/projects/' + projectName + '/end'; $.ajax({ url: innerurl, headers: { @@ -206,7 +259,7 @@ function closePhase(phase, projectName){ success: function () { location.reload(); }, - error: function(a){ + error: function (a) { } diff --git a/gemeinsamforschen/src/main/webapp/project/myCourses-student.jsp b/gemeinsamforschen/src/main/webapp/project/myCourses-student.jsp new file mode 100644 index 0000000000000000000000000000000000000000..403cd25aa276f6f1c3008c7fb52a663f194a82c0 --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/project/myCourses-student.jsp @@ -0,0 +1,67 @@ +<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> + +<!DOCTYPE html> +<html> + +<head> + <omniDependencies:omniDependencies hierarchy="1"/> + <link rel="stylesheet" href="css/all.css"> + <link rel="stylesheet" href="css/style.css" type="text/css"> + <link rel="stylesheet" href="css/normalize.css" type="text/css"> + <link href="https://fonts.googleapis.com/css?family=Nunito:400,700" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css?family=Nunito:300,400,700" rel="stylesheet"> + <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous"> + + <script src="js/myCourses-student.js"></script> +</head> + +<body> +<menu:menu hierarchy="1"/> +<div> + <div class="col span_2_of_2 centered"> + <h1>Meine Kurse</h1> + <p class="introduction"> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p> + + <!-- filter--> + <div class="filter"> + <label>Ich suche nach: <select> + <script id="searchingTemplate" type="text/x-jQuery-tmpl"> + <option value="${searchOption}">${searchOption}</option> + </script> + <option value="saab">Saab</option> + <option value="mercedes">Mercedes</option> + <option value="audi">Audi</option> + </select></label> + <div class="select_arrow"></div> + </div> + <div class="search"> + <input type="text" name="suche" placeholder="Suche"><i class="fas fa-search"></i> + </div> + </div> + + <div class="row group projects-grid" id="projects"> + <script id="projectTemplate" type="text/x-jQuery-tmpl"> + <div class="card"> + <div> + <h3>${projectName}</h3> + <p> + ${projectDescription} + </p> + <button class="primary" id="project_${projectName}">Einsehen </button> + </div> + </div> + </script> + </div> +</div> +<button class="btn btn-default" type="button" style="margin-left:250px;" id="enrollProject">Projekt suchen +</button> +<footer:footer/> + + +</body> + +</html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/overview-docent.jsp b/gemeinsamforschen/src/main/webapp/project/overview-docent.jsp index 19f5447c8aba0f53cec48181271dbde977990256..d99bdd6c6d1250c71045f5cb4bc99bff03366133 100644 --- a/gemeinsamforschen/src/main/webapp/project/overview-docent.jsp +++ b/gemeinsamforschen/src/main/webapp/project/overview-docent.jsp @@ -2,6 +2,7 @@ <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> <!DOCTYPE html> <html> @@ -13,12 +14,8 @@ <body> <menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <headLine:headLine/> - - <table id="projects"> <!-- getElementById('projects').append um neue Projekte anzufügen --> - <script id="projectTRTemplate" type="text/x-jQuery-tmpl"> +<table id="projects"> <!-- getElementById('projects').append um neue Projekte anzufügen --> + <script id="projectTRTemplate" type="text/x-jQuery-tmpl"> <tr class="pageChanger"> <td> <a id="project${projectName}"> @@ -52,13 +49,14 @@ <td></td> </tr> - </script> - </table> - <button class="btn btn-default" type="button" id="createProject" style="margin-left:250px;">Projekt - erstellen - </button> - </div> + </script> +</table> + +<button class="btn btn-default" type="button" id="createProject" style="margin-left:250px;">Projekt + erstellen +</button> +<footer:footer/> </body> </html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/overview-student.jsp b/gemeinsamforschen/src/main/webapp/project/overview-student.jsp index b2457201073c9d1d971ac0c7701e42804ebf8c40..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/gemeinsamforschen/src/main/webapp/project/overview-student.jsp +++ b/gemeinsamforschen/src/main/webapp/project/overview-student.jsp @@ -1,66 +0,0 @@ -<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> - -<!DOCTYPE html> -<html> - -<head> - <omniDependencies:omniDependencies hierarchy="1"/> - <script src="js/overview-student.js"></script> -</head> - -<body> -<menu:menu hierarchy="1"/> -<div id="wrapper"> - <div class="page-content-wrapper"> - <headLine:headLine/> - <div> - <table id="projects"> <!-- getElementById('projects').append um neue Projekte anzufügen --> - <script id="projectTRTemplate" type="text/x-jQuery-tmpl"> - <tr class="pageChanger"> - <td> - <a id="project_${projectName}"> - <h1>${projectName}</h1> - </a> - </td> - </tr> - <tr> - <td> - <div class="panel panel-default"> - <div class="panel-heading"> - <h3 class="panel-title">Newsfeed </h3> - Status: <p id="status_${projectName}"></p> - </div> - <div class="panel-body"> - <ul class="list-group"> - <li class="list-group-item"> - <span>dummy</span> - </li> - <li class="list-group-item"> - <span>dummy</span> - </li> - <li class="list-group-item"> - <span>dummy</span></li> - </ul> - </div> - </div> - </td> - </tr> - <tr> - <td></td> - </tr> - - </script> - </table> - </div> - <button class="btn btn-default" type="button" style="margin-left:250px;" id="enrollProject">Projekt beitreten - </button> - </div> -</div> - - -</body> - -</html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/search-project.jsp b/gemeinsamforschen/src/main/webapp/project/search-project.jsp new file mode 100644 index 0000000000000000000000000000000000000000..02b38d5519988a0ba6d60c36403de788ed673577 --- /dev/null +++ b/gemeinsamforschen/src/main/webapp/project/search-project.jsp @@ -0,0 +1,65 @@ +<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="menu" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> + +<!DOCTYPE html> +<html> + +<head> + <omniDependencies:omniDependencies hierarchy="1"/> + <link rel="stylesheet" href="css/all.css"> + <link rel="stylesheet" href="css/style.css" type="text/css"> + <link rel="stylesheet" href="css/normalize.css" type="text/css"> + <link href="https://fonts.googleapis.com/css?family=Nunito:400,700" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css?family=Nunito:300,400,700" rel="stylesheet"> + <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous"> + + <script src="js/search-project.js"></script> +</head> + +<body> +<menu:menu hierarchy="1"/> +<div> + <div class="col span_2_of_2 centered"> + <h1>Projektsuche</h1> + <p class="introduction"> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p> + + <!-- filter--> + <div class="filter"> + <label>Ich suche nach: <select> + <script id="searchingTemplate" type="text/x-jQuery-tmpl"> + <option value="${searchOption}">${searchOption}</option> + </script> + <option value="saab">Saab</option> + <option value="mercedes">Mercedes</option> + <option value="audi">Audi</option> + </select></label> + <div class="select_arrow"></div> + </div> + <div class="search"> + <input type="text" name="suche" placeholder="Suche"><i class="fas fa-search"></i> + </div> + </div> + + <div class="row group projects-grid" id="projects"> + <script id="projectTemplate" type="text/x-jQuery-tmpl"> + <div class="card"> + <div> + <h3>${projectName}</h3> + <p> + ${projectDescription} + </p> + <button class="primary" id="project_${projectName}">Beitreten </button> + </div> + </div> + </script> + </div> +</div> +<footer:footer/> + + +</body> + +</html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/project/tasks-docent.jsp b/gemeinsamforschen/src/main/webapp/project/tasks-docent.jsp index 8c40df687e33d099529ad25e56337497a7a6484a..fa6873db3894506e11cc5287f3447cc504adeaa5 100644 --- a/gemeinsamforschen/src/main/webapp/project/tasks-docent.jsp +++ b/gemeinsamforschen/src/main/webapp/project/tasks-docent.jsp @@ -14,42 +14,36 @@ </head> <body> <menu:menu hierarchy="1"/> -<div class="col span_l_of_2"> <!-- col right--> - <headLine:headLine/> - <div id="listOfTasks"> </div> <script id="taskTemplate" type="text/x-jQuery-tmpl"> - {{if taskProgress === "FINISHED"}} - <div class="card-finished"><h4 class="icon closed">${infoText}</h4> - {{html timeFrame}} - </div> - {{else}} - <div class="card ${phase}"> - - <div class="col span_s_of_2 icon ${taskType}"> - </div> - <div class="col span_l_of_2" id="${taskName}"> - {{if infoText}} - <h4>${infoText}</h4> - {{/if}} - {{if solveTaskWith}} - <button class='primary' onClick='${solveTaskWithLink}'>${solveTaskWith}</button> - {{/if}} - {{if helpLink}} - <div style="width:100%"><a href='${helpLink}'>Hier</a> bekommst du Hilfe.</div> - {{/if}} - </div> - {{if timeFrame}} - {{html timeFrame}} - {{/if}} - <div style="clear:left"></div> - </div> - </div> - {{/if}} + <div class="card ${phase}"> + <div class="col span_s_of_2 icon ${taskType}"> + </div> + <div class="col span_l_of_2" id="${taskName}"> + {{if infoText}} + <h4>${infoText}</h4> + {{/if}} + {{if solveTaskWith}} + <button class='primary' onClick='${solveTaskWithLink}'>${solveTaskWith}</button> + {{/if}} + {{if helpLink}} + <div style="width:100%"><a href='${helpLink}'>Hier</a> bekommst du Hilfe.</div> + {{/if}} + </div> + {{if timeFrame}} + {{html timeFrame}} + {{/if}} + <div style="clear:left"></div> + </div> + </div> +</script> +<script id="finishedTaskTemplate" type="text/x-jQuery-tmpl"> + <div class="card-finished"><h4 class="icon closed">${infoText}</h4> + {{html timeFrame}} + </div> </script> -</div> <footer:footer/> </body> </html> diff --git a/gemeinsamforschen/src/main/webapp/project/tasks-student.jsp b/gemeinsamforschen/src/main/webapp/project/tasks-student.jsp index 96009f17e985f63b6ae033afe5119105f72a9793..c13ff10484c3a680e2758bc5a33ad05415cd979c 100644 --- a/gemeinsamforschen/src/main/webapp/project/tasks-student.jsp +++ b/gemeinsamforschen/src/main/webapp/project/tasks-student.jsp @@ -10,7 +10,7 @@ <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="headLine" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="omniDependencies" %> <%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="footer" %> -<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="chat"%> +<%@ taglib uri="../taglibs/gemeinsamForschen.tld" prefix="chat" %> <!DOCTYPE html> <html lang="de"> @@ -20,18 +20,16 @@ </head> <body> <menu:menu hierarchy="1"/> -<div class="col span_l_of_2"> <!-- col right--> - <headLine:headLine/> - <%-- <div class="infotext "> - <p class="icon">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt - ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et - ea rebum. - </p> - </div>--%> - <div id="listOfTasks"> +<%-- <div class="infotext "> + <p class="icon">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt + ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et + ea rebum. + </p> +</div>--%> +<div id="listOfTasks"> - </div> - <script id="taskTemplate" type="text/x-jQuery-tmpl"> +</div> +<script id="taskTemplate" type="text/x-jQuery-tmpl"> <div class="card ${phase}"> <div class="col span_s_of_2 icon ${taskType}"> @@ -53,11 +51,20 @@ <div style="clear:left"></div> </div> </div> - </script> - <chat:chatWindow orientation="right"></chat:chatWindow> +</script> +<script id="finishedTaskTemplate" type="text/x-jQuery-tmpl"> +<div class="card ${phase}"> + <div class="card-finished"><h4 class="icon closed">${infoText}</h4> + {{html timeFrame}} + </div> </div> +</script> + +<chat:chatWindow orientation="right" scope="project"></chat:chatWindow> +<chat:chatWindow orientation="right" scope="group"></chat:chatWindow> + <footer:footer/> </body> diff --git a/gemeinsamforschen/src/main/webapp/project/tasks-student.jsp.orig b/gemeinsamforschen/src/main/webapp/project/tasks-student.jsp.orig deleted file mode 100644 index 2b613340392efcebd45ab65dbcf98027ff35f3d8..0000000000000000000000000000000000000000 --- a/gemeinsamforschen/src/main/webapp/project/tasks-student.jsp.orig +++ /dev/null @@ -1,221 +0,0 @@ -<%-- - Created by IntelliJ IDEA. - User: dehne - Date: 04.10.2018 - Time: 11:24 - To change this template use File | Settings | File Templates. ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<!DOCTYPE html> -<html lang="de"> -<head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <link rel="stylesheet" href="css/all.css"> - <link rel="stylesheet" href="css/style.css" type="text/css"> - <link rel="stylesheet" href="css/normalize.css" type="text/css"> - <link href="https://fonts.googleapis.com/css?family=Nunito:400,700" rel="stylesheet"> - <link href="https://fonts.googleapis.com/css?family=Nunito:300,400,700" rel="stylesheet"> - <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous"> - - <title>Projecttitle | Gemeinsam Forschen</title> -</head> -<body> -<<<<<<< HEAD -<menu:menu hierarchy="1"/> -<div id="wrapper"> -======= -<%--<header> - <div class="row "> - <div class="nav-group-left"> - <a>Home</a> - <a>Meine Projekte</a> - </div> - <div class="nav-group-right"> - <a>Wiki</a> - <a>Einstellungen</a> - <a>Logout</a> - - </div> - </div> - - -</header>--%> ->>>>>>> origin/development_master - -<main> - <div class="row group"> - <div class="titlerow"> - - </div> - </div> - - <div class="row group nav"> - <a href="" >[<i class="fas fa-chevron-left"> zurueck ]</i></a> - </div> - - <div class="row group"> - - <div class="col span_s_of_2 .timeline"> - <ul> - <li class="neutral icon closed">Projektinitialisierung</li> - <li class="draft icon closed">Entwurfsphase</li> - <li class="feedback icon ">Feedbackphase</li> - <li class="icon inactive">Reflextionsphase</li> - <li class="icon inactive" >Assessment</li> - <li class="icon inactive">Noten</li> - - </ul> - </div> - - <div class="col span_l_of_2"> <!-- col right--> - <h1>Projektname</h1> - - <div class="infotext "> - <p class="icon">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. - </p> - </div> - - <!-- Aufgabe --> - <div class="card "> - <div class="col span_s_of_2 icon infotask"> - - </div> - - <div class="col span_l_of_2"> - <h4>Du wurdest einer Forschungsgruppe hinzugefügt</h4> - </div> - - - <div style="clear:left"></div> - - </div> - - <!-- Aufgabe --> - <div class="card card-draft"> - <div class="col span_s_of_2 icon grouptask"> - - </div> - - <div class="col span_l_of_2"> - <button class="primary">Lege ein Dossier an </button> - <a href="#">Erfahre etwas über Forschungsdossiers</a> - </div> - - <div class="status icon"><p>Noch drei Tage Zeit</p></div> - <div style="clear:left"></div> - - </div> - <!-- Aufgabe --> - <div class="card card-feedback"> - <div class="col span_s_of_2 icon infotask"> - </div> - - <div class="col span_l_of_2"> - <h4> Erhalte 3 Feedbacks </h4> - <div class="shoulds"> - <i class="fas fa-check-circle"></i> - <i class="fas fa-check-circle"></i> - <i class="far fa-circle"></i> - - </div> - <ul class="list"> - <li><a href="#">Feedback 1</a></li> - <li><a href="#">Feedback 2</a></li> - - </ul> - </div> - <div class="status icon"><p>Noch drei Tage Zeit</p></div> - <div style="clear:left"></div> - - </div> - - <!-- Aufgabe --> - <div class="card card-reflection"> - <div class="col span_s_of_2 icon usertask"> - </div> - - <div class="col span_l_of_2"> - <button class="primary">Schreibe ein Feedback </button> - <a href="#">Regeln fürs Feedback schreiben</a> - </div> - <div class="status alert icon"><p>Du bist zu spät.</p></div> - <div style="clear:left"></div> - - </div> - - - <!-- Aufgabe --> - <div class="card card-presentation "> - <div class="col span_s_of_2 icon grouptask "> - - </div> - - <div class="col span_l_of_2"> - <button class="primary">Schließe das Dossier ab </button> - <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.</p> - </div> - <div class="status icon"></div> - <div style="clear:left"></div> - - </div> - - <!-- Aufgabe --> - <div class="card card-assessment "> - <div class="col span_s_of_2 icon grouptask "> - - </div> - - <div class="col span_l_of_2"> - <button class="primary">Schließe das Dossier ab </button> - <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.</p> - </div> - <div class="status icon"><p>Noch drei Tage Zeit</p></div> - <div style="clear:left"></div> - - </div> - - <!-- Aufgabe --> - <div class="card card-grades "> - <div class="col span_s_of_2 icon grouptask "> - - </div> - - <div class="col span_l_of_2"> - <button class="primary">Schließe das Dossier ab </button> - <br> - <input type="checkbox" name="vehicle1" value="Bike"> Is erledigt <br> - <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.</p> - </div> - <div class="status icon"></div> - <div style="clear:left"></div> - - </div> - </div> - </div> - - <div style="clear:left"></div> - <!-- schmale spalte links, weite spalte rechts--> - <!-- <div class="row group"> - - <div class="col span_l_of_2"> - This is column 1 - </div> - - <div class="col span_s_of_2"> - This is column 2 - </div> - - </div> --> - -</main> -<footer> - <p> - Impressum </br> - Ansprechpartner</br> - Fides</br> - </p> -</footer> - -</body> -</html> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/register.jsp b/gemeinsamforschen/src/main/webapp/register.jsp index 6cc51b088ff3c79435f4d5554c6e1904de78fce8..06bc1c278af5000f5b7eee773c16dc2b51d1dde1 100644 --- a/gemeinsamforschen/src/main/webapp/register.jsp +++ b/gemeinsamforschen/src/main/webapp/register.jsp @@ -19,7 +19,7 @@ <body> <div class="login-clean"> <%--<form method="post" action="./servlet/createUser">--%> - <form method="post" action="./rest/user/create"> + <form method="post" action="rest/user/create"> <h2 class="sr-only">Login Formular</h2> <div class="illustration"><img src="libs/img/fides-logo.svg"></div> <div class="form-group"><input class="form-control" name="name" placeholder="Name"></div> diff --git a/gemeinsamforschen/src/main/webapp/researchReport/js/createReportOverview.js b/gemeinsamforschen/src/main/webapp/researchReport/js/createReportOverview.js index b1542e3a5fe34c84bc2438bb0246fb408ab5b6a6..03f88c50b38d9c788be33d16ad528985aaa3e53f 100644 --- a/gemeinsamforschen/src/main/webapp/researchReport/js/createReportOverview.js +++ b/gemeinsamforschen/src/main/webapp/researchReport/js/createReportOverview.js @@ -9,7 +9,7 @@ $(document).ready(function () { //boolean uploaded=true; }); $('#backLink').on('click', function () { - location.href = "overview-student.jsp"; + location.href = "search-project.jsp"; return false; }); diff --git a/gemeinsamforschen/src/main/webapp/taglibs/gemeinsamForschen.tld b/gemeinsamforschen/src/main/webapp/taglibs/gemeinsamForschen.tld index a855fabaed419a9e66f4073d7a3f784c76f42feb..d35f8921256ef582357796a0eda711b3cbeddeb6 100644 --- a/gemeinsamforschen/src/main/webapp/taglibs/gemeinsamForschen.tld +++ b/gemeinsamforschen/src/main/webapp/taglibs/gemeinsamForschen.tld @@ -62,6 +62,12 @@ <required>yes</required> <rtexprvalue>no</rtexprvalue> </attribute> + + <attribute> + <name>scope</name> + <required>yes</required> + <rtexprvalue>no</rtexprvalue> + </attribute> </tag> </taglib> \ No newline at end of file diff --git a/gemeinsamforschen/src/main/webapp/taglibs/js/utility.js b/gemeinsamforschen/src/main/webapp/taglibs/js/utility.js index 7a174d902942ed373983f14fc0334e2bcb99b97d..c5f382243d4a3e8286ef233a40e531221a78bb7d 100644 --- a/gemeinsamforschen/src/main/webapp/taglibs/js/utility.js +++ b/gemeinsamforschen/src/main/webapp/taglibs/js/utility.js @@ -101,13 +101,18 @@ function RequestObj(hierachyLevel, modulePath, methodPath, pathParams, queryPara } +function serverSide(requestObj, method, callback) { + serverSideWithType(requestObj, method, callback, 'application/json'); +} + /** * send a request to the server * @param requestObj the data specific to the reqeuest * @param method GET, POST, DELETE or PUT * @param callback + * @param contentType typically 'application/json' */ -function serverSide(requestObj, method, callback) { +function serverSideWithType(requestObj, method, callback, contentType) { let relativPath = calculateHierachy(requestObj.hierarchyLevel) let methodPath = requestObj.methodPath requestObj.pathParams.forEach(function (e) { @@ -120,10 +125,11 @@ function serverSide(requestObj, method, callback) { localurl = localurl + requestObj.queryParams; } + if (method == "PUT") { $.ajax({ url: localurl, - contentType: 'application/json', + contentType: contentType, type: 'PUT', data: JSON.stringify(requestObj.entity), success: function (response) { @@ -139,7 +145,7 @@ function serverSide(requestObj, method, callback) { if (method == "POST" || method == "DELETE") { $.ajax({ url: localurl, - contentType: 'application/json', + contentType: contentType, type: method, data: requestObj.entity == null ? {} : JSON.stringify(requestObj.entity), success: function (response) { diff --git a/gemeinsamforschen/src/scripts/dbschema/createDummyUsersGroupsProject.sql b/gemeinsamforschen/src/scripts/dbschema/createDummyUsersGroupsProject.sql new file mode 100644 index 0000000000000000000000000000000000000000..3cea0d69bbed09ff1e23688ad5c2cc8fb624105e --- /dev/null +++ b/gemeinsamforschen/src/scripts/dbschema/createDummyUsersGroupsProject.sql @@ -0,0 +1,54 @@ +/* + Make sure all tables are created with fltrail.sql + Functions with empty grouptable only (because of autoincrement id of group) + */ + +INSERT INTO `users` (`name`, `password`, `email`, `token`, `rocketChatId`, `rocketChatAuthToken`) VALUES + ('teststudent1', 'egal', 'test1@uni.de', 'test1','1','abc'); + +INSERT INTO `users` (`name`, `password`, `email`, `token`, `rocketChatId`, `rocketChatAuthToken`) VALUES + ('teststudent2', 'egal', 'test2@uni.de', 'test1','1','abc'); + +INSERT INTO `users` (`name`, `password`, `email`, `token`, `rocketChatId`, `rocketChatAuthToken`) VALUES + ('teststudent3', 'egal', 'test3@uni.de', 'test1','1','abc'); + +INSERT INTO `users` (`name`, `password`, `email`, `token`, `rocketChatId`, `rocketChatAuthToken`) VALUES + ('teststudent4', 'egal', 'test4@uni.de', 'test1','1','abc'); + +INSERT INTO `users` (`name`, `password`, `email`, `token`, `rocketChatId`, `rocketChatAuthToken`) VALUES + ('teststudent5', 'egal', 'test5@uni.de', 'test1','1','abc'); + +INSERT INTO `users` (`name`, `password`, `email`, `token`, `rocketChatId`, `rocketChatAuthToken`) VALUES + ('teststudent6', 'egal', 'test6@uni.de', 'test1','1','abc'); + +INSERT INTO `users` (`name`, `password`, `email`, `token`, `rocketChatId`, `rocketChatAuthToken`) VALUES + ('teststudent7', 'egal', 'test7@uni.de', 'test1','1','abc'); + +INSERT INTO `users` (`name`, `password`, `email`, `token`, `rocketChatId`, `rocketChatAuthToken`) VALUES + ('teststudent8', 'egal', 'test8@uni.de', 'test1','1','abc'); + +INSERT INTO `projects` (`id`, `password`,`active`,`timecreated`,`token`,`phase`) VALUES + ('1','123',1,current_timestamp,'Julian','CourseCreation'); + +INSERT INTO `projectuser` (`projectName`,`userEmail`) VALUES ('1','test1@uni.de'); +INSERT INTO `projectuser` (`projectName`,`userEmail`) VALUES ('1','test2@uni.de'); +INSERT INTO `projectuser` (`projectName`,`userEmail`) VALUES ('1','test3@uni.de'); +INSERT INTO `projectuser` (`projectName`,`userEmail`) VALUES ('1','test4@uni.de'); +INSERT INTO `projectuser` (`projectName`,`userEmail`) VALUES ('1','test5@uni.de'); +INSERT INTO `projectuser` (`projectName`,`userEmail`) VALUES ('1','test6@uni.de'); +INSERT INTO `projectuser` (`projectName`,`userEmail`) VALUES ('1','test7@uni.de'); +INSERT INTO `projectuser` (`projectName`,`userEmail`) VALUES ('1','test8@uni.de'); + +INSERT INTO groups (projectName, chatRoomId) VALUES ('1','1'); +INSERT INTO groups (projectName, chatRoomId) VALUES ('1','2'); +INSERT INTO groups (projectName, chatRoomId) VALUES ('1','3'); +INSERT INTO groups (projectName, chatRoomId) VALUES ('1','4'); + +INSERT INTO groupuser (userEmail, groupId) VALUES ('test1@uni.de','1'); +INSERT INTO groupuser (userEmail, groupId) VALUES ('test2@uni.de','2'); +INSERT INTO groupuser (userEmail, groupId) VALUES ('test3@uni.de','3'); +INSERT INTO groupuser (userEmail, groupId) VALUES ('test4@uni.de','4'); +INSERT INTO groupuser (userEmail, groupId) VALUES ('test5@uni.de','1'); +INSERT INTO groupuser (userEmail, groupId) VALUES ('test6@uni.de','2'); +INSERT INTO groupuser (userEmail, groupId) VALUES ('test7@uni.de','3'); +INSERT INTO groupuser (userEmail, groupId) VALUES ('test8@uni.de','4'); \ No newline at end of file diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/group/ProfileDAOTest.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/group/ProfileDAOTest.java new file mode 100644 index 0000000000000000000000000000000000000000..57ae0fb2a1b5d18fb4f2886c096d7349e1432888 --- /dev/null +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/group/ProfileDAOTest.java @@ -0,0 +1,41 @@ +package unipotsdam.gf.core.context.group; + +import ch.vorburger.exec.ManagedProcessException; +import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.utilities.ServiceLocatorUtilities; +import org.junit.Before; +import org.junit.Test; +import unipotsdam.gf.core.database.TestGFApplicationBinder; +import unipotsdam.gf.core.database.UpdateDB; +import unipotsdam.gf.modules.group.Group; +import unipotsdam.gf.modules.user.User; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class ProfileDAOTest { + @Before + public void setUp() throws IOException, SQLException, ManagedProcessException { + final ServiceLocator locator = ServiceLocatorUtilities.bind(new TestGFApplicationBinder()); + //final ServiceLocator locator = ServiceLocatorUtilities.bind(new GFApplicationBinder()); + locator.inject(this); + + UpdateDB.updateTestDB(); + } + + @Test + public void testCreate() { + + } + + @Test + public void testDelete() { + + } + + +} diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/project/ProjectDAOTest.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/project/ProjectDAOTest.java index 907e6c7797d0325fd836497ef7f36f0bef4849d7..85bc44943c71e38cd806f42a661cc6d14849302c 100644 --- a/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/project/ProjectDAOTest.java +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/project/ProjectDAOTest.java @@ -76,7 +76,6 @@ public class ProjectDAOTest { projectDAO.persist(project); Project projectActual = projectDAO.getProjectByName(project.getName()); - assertEquals(project.getAdminPassword(), projectActual.getAdminPassword()); assertEquals(project.getAuthorEmail(), projectActual.getAuthorEmail()); assertEquals(project.getName(), projectActual.getName()); assertEquals(project.getPassword(), projectActual.getPassword()); diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/user/UserDAOTest.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/user/UserDAOTest.java index 47c98e52f0e5bcfca2f304adaa9b49051b2a3fde..91d41f32e66cd4f53bd24482c5507a99db814547 100644 --- a/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/user/UserDAOTest.java +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/core/context/user/UserDAOTest.java @@ -90,7 +90,7 @@ public class UserDAOTest { assertTrue(userDAO.exists(user)); user.setStudent(false); - userDAO.update(user); + userDAO.updateRocketChatUserName(user); assertTrue(userDAO.exists(user)); User managementUser = userDAO.getUserByEmail(user.getEmail()); assertEquals(user.getStudent(), managementUser.getStudent()); diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/core/database/UpdateDB.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/core/database/UpdateDB.java index 17c9b042e8badd6122a1f6ac07fb8eeddcad5eeb..2485150f96f9b225170ec9aa4cd2a1074993c9f9 100644 --- a/gemeinsamforschen/src/test/java/unipotsdam/gf/core/database/UpdateDB.java +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/core/database/UpdateDB.java @@ -32,7 +32,7 @@ public class UpdateDB { System.out.println(new java.io.File( "." ).getCanonicalPath()); updateDB.runScript(new FileReader("src/test/resources/database/db.sql")); updateDB.runScript(new FileReader("src/test/resources/database/fltrail.sql")); - updateDB.runScript(new FileReader("src/test/resources/database/testuser.sql")); + //updateDB.runScript(new FileReader("src/test/resources/database/testuser.sql")); } public static void updateTestDB() throws SQLException, ManagedProcessException, IOException { diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/ActivityFlowTest.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/ActivityFlowTest.java index 4c5768678e7deeca05f904dc0cfc7bc7d1c93dd6..a45677c7f7bf52a4629281843dcb3cc84114b96f 100644 --- a/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/ActivityFlowTest.java +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/ActivityFlowTest.java @@ -13,6 +13,8 @@ import org.mockito.junit.MockitoRule; import uk.co.jemos.podam.api.PodamFactory; import uk.co.jemos.podam.api.PodamFactoryImpl; import unipotsdam.gf.core.database.TestGFApplicationBinder; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; import unipotsdam.gf.modules.project.Management; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.modules.project.ProjectConfiguration; @@ -24,8 +26,7 @@ import unipotsdam.gf.modules.user.User; import unipotsdam.gf.modules.group.GroupFormationMechanism; import unipotsdam.gf.modules.group.GroupfindingCriteria; import unipotsdam.gf.modules.journal.model.Journal; -import unipotsdam.gf.modules.feedback.Category; -import unipotsdam.gf.modules.feedback.Model.Peer2PeerFeedback; +import unipotsdam.gf.modules.annotation.model.Category; import unipotsdam.gf.modules.researchreport.ResearchReport; import unipotsdam.gf.modules.researchreport.ResearchReportManagement; @@ -53,10 +54,6 @@ public class ActivityFlowTest { @Inject ResearchReportManagement researchReportManagement; - - @Inject - Feedback feedback; - @Inject IPhases phases; @@ -92,20 +89,17 @@ public class ActivityFlowTest { public void setUp() { final ServiceLocator locator = ServiceLocatorUtilities.bind(new TestGFApplicationBinder()); locator.inject(this); - - feedback = Mockito.spy(feedback); researchReportManagement = Mockito.spy(researchReportManagement); phases = Mockito.spy(phases); iCommunication = Mockito.spy(iCommunication); // TODO @Julian: Find out more elegant way of doing this - researchReportManagement.setFeedback(feedback); // phases.setFeedback(feedback); } @Test - public void activityPlayer() { + public void activityPlayer() throws RocketChatDownException, UserDoesNotExistInRocketChatException { // register teacher loginTeacher(); @@ -192,7 +186,7 @@ public class ActivityFlowTest { } - public void uploadDossiers() { + public void uploadDossiers() throws RocketChatDownException, UserDoesNotExistInRocketChatException { for (User student : students) { @@ -201,24 +195,8 @@ public class ActivityFlowTest { researchReportManagement.createResearchReport(researchReport, project, student); } - - // assert that after the last report has been submitted, the feedback tasks were assigned automatically - verify(feedback).assignFeedbackTasks(project); - // students give feedback - for (User student : students) { - ResearchReport feedbackTask = feedback.getFeedbackTask(student); - ProjectConfiguration projectConfiguration = management.getProjectConfiguration(project); - HashMap<Category, Boolean> criteriaSelected = projectConfiguration.getCriteriaSelected(); - for (Category category : criteriaSelected.keySet()) { - if (criteriaSelected.get(category)) { - Peer2PeerFeedback peer2PeerFeedback = factory.manufacturePojo(Peer2PeerFeedback.class); - peer2PeerFeedback.setFeedbackcategory(category); - //feedback.giveFeedback(peer2PeerFeedback, feedbackTask); - // TODO implement giving feedback - } - } - } + // students upload updated dossier ArrayList<User> students2 = students; @@ -239,7 +217,6 @@ public class ActivityFlowTest { // student misses mockfeedback -> reassignment // assert that while reports are still missing mockfeedback tasks are reassigned - verify(feedback).assigningMissingFeedbackTasks(project); // assert that everybody has given and received mockfeedback assertTrue(constraints.checkIfFeedbackCanBeDistributed(project)); diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/AnnotationTest.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/AnnotationTest.java index ffb941dd20e4640a6f3a3372c53786277fdc2604..424d5c3174decb3f68473faf7fe051859a1ffeb0 100644 --- a/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/AnnotationTest.java +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/AnnotationTest.java @@ -4,14 +4,13 @@ import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.utilities.ServiceLocatorUtilities; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; import unipotsdam.gf.core.database.TestGFApplicationBinder; import unipotsdam.gf.modules.annotation.controller.AnnotationController; import unipotsdam.gf.modules.annotation.model.Annotation; import unipotsdam.gf.modules.annotation.model.AnnotationBody; import unipotsdam.gf.modules.annotation.model.AnnotationPatchRequest; import unipotsdam.gf.modules.annotation.model.AnnotationPostRequest; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import javax.inject.Inject; import java.util.ArrayList; diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/PhaseTest.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/PhaseTest.java index 2f3f29b3f834f30f5a92cd20768b451abe9a2bd2..772c7a202206b37c9579b311d5a5922a88d6b237 100644 --- a/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/PhaseTest.java +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/interfaces/PhaseTest.java @@ -7,6 +7,8 @@ import org.junit.Test; import uk.co.jemos.podam.api.PodamFactory; import uk.co.jemos.podam.api.PodamFactoryImpl; import unipotsdam.gf.config.GFApplicationBinder; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; import unipotsdam.gf.modules.project.Management; import unipotsdam.gf.modules.project.Project; import unipotsdam.gf.modules.user.User; @@ -51,28 +53,28 @@ public class PhaseTest { @Test - public void phase2() { + public void phase2() throws RocketChatDownException, UserDoesNotExistInRocketChatException { Project project = new Project(); project.setName(projectName); phases.endPhase(Phase.GroupFormation, project); } @Test - public void phase3() { + public void phase3() throws RocketChatDownException, UserDoesNotExistInRocketChatException { Project project = new Project(); project.setName(projectName); phases.endPhase(Phase.DossierFeedback, project); } @Test - public void phase4() { + public void phase4() throws RocketChatDownException, UserDoesNotExistInRocketChatException { Project project = new Project(); project.setName(projectName); phases.endPhase(Phase.Execution, project); } @Test - public void phase5() { + public void phase5() throws RocketChatDownException, UserDoesNotExistInRocketChatException { Project project = new Project(); project.setName(projectName); phases.endPhase(Phase.Assessment, project); diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/communication/service/CommunicationServiceTest.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/communication/service/CommunicationServiceTest.java index 1917b7fa90341a19e61e28112c58e167f03f9318..73e2b7c1db641c471305858d1f7fa18656523148 100644 --- a/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/communication/service/CommunicationServiceTest.java +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/communication/service/CommunicationServiceTest.java @@ -9,6 +9,9 @@ import uk.co.jemos.podam.api.PodamFactoryImpl; import unipotsdam.gf.config.GFApplicationBinder; import unipotsdam.gf.core.database.TestGFApplicationBinder; import unipotsdam.gf.core.database.UpdateDB; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; +import unipotsdam.gf.exceptions.UserExistsInRocketChatException; import unipotsdam.gf.interfaces.ICommunication; import unipotsdam.gf.modules.assessment.controller.model.StudentIdentifier; import unipotsdam.gf.modules.communication.model.EMailMessage; @@ -64,14 +67,14 @@ public class CommunicationServiceTest { @BeforeClass public static void init() throws IOException, SQLException, ManagedProcessException { - UpdateDB.updateTestDB(); + //UpdateDB.updateTestDB(); } @Before public void setUp() { - final ServiceLocator locator = ServiceLocatorUtilities.bind(new TestGFApplicationBinder()); - //final ServiceLocator locator = ServiceLocatorUtilities.bind(new GFApplicationBinder()); + //final ServiceLocator locator = ServiceLocatorUtilities.bind(new TestGFApplicationBinder()); + final ServiceLocator locator = ServiceLocatorUtilities.bind(new GFApplicationBinder()); locator.inject(this); user = new User("Vorname Nachname", "password", "email@uni.de", true); @@ -83,38 +86,42 @@ public class CommunicationServiceTest { @After public void tearDown() { - createdChatRooms.forEach(createdChatRoom -> iCommunication.deleteChatRoom(createdChatRoom)); + createdChatRooms.forEach(createdChatRoom -> { + try { + iCommunication.deleteChatRoom(createdChatRoom); + } catch (RocketChatDownException e) { + e.printStackTrace(); + } catch (UserDoesNotExistInRocketChatException e) { + e.printStackTrace(); + } + }); createdChatRooms.clear(); } @Test - public void loginUser() { - assertTrue(iCommunication.loginUser(TEST_USER)); + public void loginUser() throws RocketChatDownException, UserDoesNotExistInRocketChatException { + assertNotNull(iCommunication.loginUser(TEST_USER)); assertTrue(!TEST_USER.getRocketChatAuthToken().isEmpty()); assertTrue(!TEST_USER.getRocketChatUserId().isEmpty()); User falseLoginUser = new User("name", "password", "email", true); - assertFalse(iCommunication.loginUser(falseLoginUser)); + assertNotNull(iCommunication.loginUser(falseLoginUser)); } @Ignore @Test - public void registerUser() { + public void registerUser() throws RocketChatDownException, UserExistsInRocketChatException { // TODO Side effect is not optimal because you need to know that before persisting the user boolean userCreated = iCommunication.registerUser(user); //userDAO.persist(user, null); assertTrue(userCreated); - assertNotNull(user.getRocketChatUserId()); - assertNotNull(user.getRocketChatAuthToken()); - assertNotNull(user.getRocketChatPersonalAccessToken()); - } @Test - public void createEmptyChatRoom() { + public void createEmptyChatRoom() throws RocketChatDownException, UserDoesNotExistInRocketChatException { String chatRoom = iCommunication.createEmptyChatRoom("Test", false); assertNotNull(chatRoom); assertFalse(chatRoom.isEmpty()); @@ -130,7 +137,7 @@ public class CommunicationServiceTest { } @Test - public void createChatRoomWithUser() { + public void createChatRoomWithUser() throws RocketChatDownException, UserDoesNotExistInRocketChatException { List<User> userList = Arrays.asList(ADMIN_USER, TEST_USER); String chatRoom = iCommunication.createChatRoom("ChatWithUser", false, userList); @@ -142,7 +149,7 @@ public class CommunicationServiceTest { } @Test - public void createChatRoomWithGroup() { + public void createChatRoomWithGroup() throws RocketChatDownException, UserDoesNotExistInRocketChatException { Group group = new Group(); group.setMembers(Collections.singletonList(ADMIN_USER)); group.setProjectName("chatWithGroup"); @@ -152,7 +159,7 @@ public class CommunicationServiceTest { } @Test - public void getChatRoomName() { + public void getChatRoomName() throws RocketChatDownException, UserDoesNotExistInRocketChatException { String expectedChatRoomName = "ChatRoomName"; String chatRoomId = iCommunication.createEmptyChatRoom(expectedChatRoomName, false); assertNotNull(chatRoomId); @@ -168,7 +175,7 @@ public class CommunicationServiceTest { } @Test - public void getChatRoomLink() { + public void getChatRoomLink() throws RocketChatDownException, UserDoesNotExistInRocketChatException { String projectId = "Projekt"; Project project = new Project(projectId, user.getEmail()); projectDAO.persist(project); @@ -192,7 +199,7 @@ public class CommunicationServiceTest { } @Test - public void exists() { + public void exists() throws RocketChatDownException, UserDoesNotExistInRocketChatException { String expectedChatRoomName = "ChatRoomName"; String chatRoomId = iCommunication.createEmptyChatRoom(expectedChatRoomName, false); assertNotNull(chatRoomId); @@ -205,7 +212,7 @@ public class CommunicationServiceTest { } @Test - public void addUserToChatRoom() { + public void addUserToChatRoom() throws RocketChatDownException, UserDoesNotExistInRocketChatException { String chatRoomId = iCommunication.createEmptyChatRoom("addUser", false); assertNotNull(chatRoomId); assertFalse(chatRoomId.isEmpty()); @@ -216,7 +223,7 @@ public class CommunicationServiceTest { } @Test - public void removeUserFromChatRoom() { + public void removeUserFromChatRoom() throws RocketChatDownException, UserDoesNotExistInRocketChatException { String chatRoomId = iCommunication.createEmptyChatRoom("removeUser", false); assertNotNull(chatRoomId); assertFalse(chatRoomId.isEmpty()); @@ -229,7 +236,7 @@ public class CommunicationServiceTest { } @Test - public void deleteChatRoom() { + public void deleteChatRoom() throws RocketChatDownException, UserDoesNotExistInRocketChatException { String chatRoomId = iCommunication.createEmptyChatRoom("deleteChatRoom", false); assertNotNull(chatRoomId); assertFalse(chatRoomId.isEmpty()); @@ -271,7 +278,7 @@ public class CommunicationServiceTest { @Test @Ignore - public void createTestData() { + public void createTestData() throws RocketChatDownException, UserDoesNotExistInRocketChatException { User user = new User(); user.setName("Martin Nachname"); user.setPassword("test1234"); @@ -282,7 +289,7 @@ public class CommunicationServiceTest { /* try { - userService.login(true, user); + userService.register(true, user); } catch (URISyntaxException e) { Assert.fail(); }*/ diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/journal/model/dao/JournalDAOImplTest.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/journal/model/dao/JournalDAOImplTest.java index f262e44d8f7c5251e8c56590719230c5dd17b2d7..5bec3c7c6231c021586b14e3a6d8cf8483c98297 100644 --- a/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/journal/model/dao/JournalDAOImplTest.java +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/journal/model/dao/JournalDAOImplTest.java @@ -9,7 +9,7 @@ import unipotsdam.gf.modules.journal.model.Journal; import unipotsdam.gf.modules.journal.model.JournalFilter; import unipotsdam.gf.modules.journal.model.Visibility; import unipotsdam.gf.modules.journal.util.JournalUtils; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import javax.inject.Inject; import java.util.ArrayList; diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/journal/service/JournalServiceImplTest.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/journal/service/JournalServiceImplTest.java index 22d11293aa01c45dcdefaa3aa4b4b5ca5d77e446..ec14976835ae5f27c75236caf80c9730cebfe9f2 100644 --- a/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/journal/service/JournalServiceImplTest.java +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/modules/journal/service/JournalServiceImplTest.java @@ -10,7 +10,7 @@ import unipotsdam.gf.modules.journal.model.Journal; import unipotsdam.gf.modules.journal.model.Visibility; import unipotsdam.gf.modules.journal.model.dao.JournalDAO; import unipotsdam.gf.modules.journal.model.dao.JournalDAOImpl; -import unipotsdam.gf.modules.feedback.Category; +import unipotsdam.gf.modules.annotation.model.Category; import javax.inject.Inject; import java.util.ArrayList; diff --git a/gemeinsamforschen/src/test/java/unipotsdam/gf/process/tasks/GroupPhaseTaskTest.java b/gemeinsamforschen/src/test/java/unipotsdam/gf/process/tasks/GroupPhaseTaskTest.java index 27ee1232b5686784b645b6d07d0017d4058c59ba..af8fbb9c08de24e86c5e5d8da3df1e23d6143abc 100644 --- a/gemeinsamforschen/src/test/java/unipotsdam/gf/process/tasks/GroupPhaseTaskTest.java +++ b/gemeinsamforschen/src/test/java/unipotsdam/gf/process/tasks/GroupPhaseTaskTest.java @@ -1,14 +1,24 @@ package unipotsdam.gf.process.tasks; +import ch.vorburger.exec.ManagedProcessException; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.utilities.ServiceLocatorUtilities; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import uk.co.jemos.podam.api.PodamFactory; import uk.co.jemos.podam.api.PodamFactoryImpl; +import unipotsdam.gf.config.GFApplicationBinder; import unipotsdam.gf.core.database.TestGFApplicationBinder; +import unipotsdam.gf.core.database.UpdateDB; +import unipotsdam.gf.exceptions.RocketChatDownException; +import unipotsdam.gf.exceptions.UserDoesNotExistInRocketChatException; +import unipotsdam.gf.exceptions.UserExistsInMysqlException; +import unipotsdam.gf.exceptions.UserExistsInRocketChatException; +import unipotsdam.gf.interfaces.ICommunication; import unipotsdam.gf.interfaces.IGroupFinding; import unipotsdam.gf.modules.group.Group; +import unipotsdam.gf.modules.group.GroupData; import unipotsdam.gf.modules.group.GroupFormationMechanism; import unipotsdam.gf.modules.project.Management; import unipotsdam.gf.modules.project.Project; @@ -18,8 +28,11 @@ import unipotsdam.gf.process.ProjectCreationProcess; import javax.inject.Inject; +import java.io.IOException; +import java.sql.SQLException; import java.util.ArrayList; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class GroupPhaseTaskTest { @@ -40,61 +53,82 @@ public class GroupPhaseTaskTest { @Inject private GroupFormationProcess groupFormationProcess; + @Inject + private ICommunication communication; + private PodamFactory factory = new PodamFactoryImpl(); - private User teacher; + @Before - public void setUp() { - /*final ServiceLocator locator = ServiceLocatorUtilities.bind(new GFApplicationBinder());*/ - final ServiceLocator locator = ServiceLocatorUtilities.bind(new TestGFApplicationBinder()); + public void setUp() throws IOException, SQLException, ManagedProcessException { + UpdateDB.main(new String[0]); + + final ServiceLocator locator = ServiceLocatorUtilities.bind(new GFApplicationBinder()); + //final ServiceLocator locator = ServiceLocatorUtilities.bind(new TestGFApplicationBinder()); locator.inject(this); } + @Ignore @Test - public void createCourse() { - - this.teacher = factory.manufacturePojo(User.class); + public void createUser() + throws RocketChatDownException, UserExistsInRocketChatException, UserExistsInMysqlException, UserDoesNotExistInRocketChatException { + User teacher = factory.manufacturePojo(User.class); + teacher.setEmail("vodka@yolo.com"); + teacher.setPassword("egal"); teacher.setStudent(false); - management.create(teacher, null); - // add Titel - Project project = factory.manufacturePojo(Project.class); - project.setAuthorEmail(teacher.getEmail()); - management.create(project); - management.register(teacher, project, null); + projectCreationProcess.deleteUser(teacher); + projectCreationProcess.createUser(teacher); + projectCreationProcess.deleteUser(teacher); + } - /*ProjectConfiguration projectConfiguration = factory.manufacturePojo(ProjectConfiguration.class); - management.create(projectConfiguration, project); + @Test + public void createCourse() + throws RocketChatDownException, UserDoesNotExistInRocketChatException, UserExistsInMysqlException, UserExistsInRocketChatException, IOException { - GroupfindingCriteria groupfindingCriteria = factory.manufacturePojo(GroupfindingCriteria.class); - groupFinding.selectGroupfindingCriteria(groupfindingCriteria, project);*/ + // create teacher + User teacher = factory.manufacturePojo(User.class); + teacher.setEmail("vodka@yolo.com"); + teacher.setPassword("egal"); + teacher.setStudent(false); + projectCreationProcess.deleteUser(teacher); + projectCreationProcess.createUser(teacher); - taskDAO.createTaskWaitForParticipants(project, teacher); - ArrayList<Task> tasks = taskDAO.getTasks(teacher, project); - assertTrue(tasks != null && tasks.size() > 0); + Project project = factory.manufacturePojo(Project.class); + project.setName("TEST"); + projectCreationProcess.deleteProject(project); + projectCreationProcess.createProject(project, teacher); + groupFormationProcess.setGroupFormationMechanism(GroupFormationMechanism.Manual, project); ArrayList<User> students = new ArrayList<>(); - for (int i = 0; i<5;i++) { + for (int i = 0; i < 30; i++) { User user = factory.manufacturePojo(User.class); user.setStudent(true); - students.add(user); - - management.create(user, null); + user.setRocketChatUsername("student" + i); + user.setEmail("student" + i + "@stuff.com"); + user.setPassword("egal"); + projectCreationProcess.deleteUser(user); + projectCreationProcess.createUser(user); projectCreationProcess.studentEntersProject(project, user); + students.add(user); } + //groupFormationProcess.changeGroupFormationMechanism(GroupFormationMechanism.Manual, project); + GroupData orInitializeGroups = groupFormationProcess.getOrInitializeGroups(project); + assertFalse(orInitializeGroups.getGroups().isEmpty()); + /* groupFormationProcess.finalize(project);*/ - groupFormationProcess.changeGroupFormationMechanism(GroupFormationMechanism.Manual, project); - Group group = new Group(); - for (User student : students) { - group.addMember(student); - } - groupFormationProcess.finish(project, group); + /*ArrayList<Task> tasks = taskDAO.getTasks(teacher, project); + assertTrue(tasks != null && tasks.size() > 0);*/ + /* for (User student : students) { + projectCreationProcess.deleteUser(student); + projectCreationProcess.deleteProject(project); + }*/ } diff --git a/gemeinsamforschen/src/test/resources/database/fltrail.sql b/gemeinsamforschen/src/test/resources/database/fltrail.sql index a1d352a9667c1e8238f4df80e751d9f626e1cf5b..2808e62c373f0b7b76f8537cd8b4614607096075 100644 --- a/gemeinsamforschen/src/test/resources/database/fltrail.sql +++ b/gemeinsamforschen/src/test/resources/database/fltrail.sql @@ -1,7 +1,7 @@ CREATE TABLE `annotations` ( `id` varchar(120) NOT NULL, `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `userEmail` varchar(120) DEFAULT NULL, + `userEmail` varchar(255) DEFAULT NULL, `targetId` varchar(120) DEFAULT NULL, `targetCategory` varchar(30) NOT NULL, `title` varchar(120) DEFAULT NULL, @@ -11,9 +11,9 @@ CREATE TABLE `annotations` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `answeredquiz` ( - `projectName` varchar(400) NOT NULL, - `userName` varchar(400) NOT NULL, - `question` varchar(400) NOT NULL, + `projectName` varchar(200) NOT NULL, + `userName` varchar(100) NOT NULL, + `question` varchar(200) NOT NULL, `correct` tinyint(4) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -28,9 +28,9 @@ CREATE TABLE `categoriesselected` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `contributionrating` ( - `projectName` varchar(400) NOT NULL, - `userName` varchar(400) NOT NULL, - `fromPeer` varchar(400) NOT NULL, + `projectName` varchar(200) NOT NULL, + `userName` varchar(100) NOT NULL, + `fromPeer` varchar(100) NOT NULL, `dossier` int(11) NOT NULL, `eJournal` int(11) NOT NULL, `research` int(11) NOT NULL @@ -39,16 +39,16 @@ CREATE TABLE `contributionrating` ( CREATE TABLE `fullsubmissions` ( `id` varchar(120) NOT NULL, `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `user` varchar(120) NOT NULL, + `user` varchar(100) NOT NULL, `text` mediumtext NOT NULL, - `projectName` varchar(120) NOT NULL, - `feedbackUser` varchar (255), - `finalized` tinyint(4) + `projectName` varchar(200) NOT NULL, + `feedbackUser` varchar(255) DEFAULT NULL, + `finalized` tinyint(1) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `grades` ( - `projectName` varchar(400) NOT NULL, - `userName` varchar(400) NOT NULL, + `projectName` varchar(200) NOT NULL, + `userName` varchar(100) NOT NULL, `grade` double NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -58,9 +58,10 @@ CREATE TABLE `groupfindingmechanismselected` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `groups` ( - `id` int(11) NOT NULL, - `projectName` varchar(400) NOT NULL, - `chatRoomId` varchar(400) + `id` int(11) NOT NULL, + `projectName` varchar(200) NOT NULL, + `chatRoomId` varchar(400) NOT NULL, + `name` varchar(255) NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `groupuser` ( @@ -68,6 +69,16 @@ CREATE TABLE `groupuser` ( `groupId` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `journals` ( + `id` varchar(100) NOT NULL, + `userEmail` varchar(100) NOT NULL, + `projectName` varchar(200) NOT NULL, + `text` text NOT NULL, + `visibility` varchar(100) NOT NULL, + `category` varchar(100) NOT NULL, + `open` tinyint(4) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + CREATE TABLE `phasesselected` ( `projectName` varchar(100) NOT NULL, `phaseSelected` varchar(200) NOT NULL @@ -75,9 +86,9 @@ CREATE TABLE `phasesselected` ( CREATE TABLE `projects` ( `name` varchar(100) NOT NULL, - `password` varchar(400) DEFAULT '', - `active` tinyint(1) NOT NULL DEFAULT true, - `timecreated` mediumtext, + `password` varchar(400) NOT NULL, + `active` tinyint(1) NOT NULL, + `timecreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `author` varchar(100) NOT NULL, `adminPassword` varchar(400), `phase` varchar(400) NOT NULL DEFAULT 'GroupFormation' @@ -85,13 +96,13 @@ CREATE TABLE `projects` ( CREATE TABLE `projectuser` ( `projectName` varchar(100) NOT NULL, - `userEmail` varchar(100) NOT NULL + `userEmail` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `quiz` ( - `author` varchar(400) NOT NULL, - `projectName` varchar(400) NOT NULL, - `question` varchar(400) NOT NULL, + `author` varchar(100) NOT NULL, + `projectName` varchar(200) NOT NULL, + `question` varchar(200) NOT NULL, `mcType` varchar(400) NOT NULL, `answer` varchar(400) NOT NULL, `correct` tinyint(1) NOT NULL @@ -106,26 +117,26 @@ CREATE TABLE `submissionpartbodyelements` ( CREATE TABLE `submissionparts` ( `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `userEmail` varchar(120) NOT NULL, + `userEmail` varchar(255) NOT NULL, `fullSubmissionId` varchar(120) NOT NULL, `category` varchar(30) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `tags` ( - `projectName` varchar(100) NOT NULL, + `projectName` varchar(200) NOT NULL, `tag` varchar(400) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `tasks` ( - `userEmail` varchar(100) NOT NULL, + `userEmail` varchar(255) NOT NULL, `projectName` varchar(200) NOT NULL, `taskName` varchar(100) DEFAULT NULL, `groupTask` tinyint(4) DEFAULT NULL, `importance` varchar(100) DEFAULT NULL, `progress` varchar(100) DEFAULT NULL, `phase` varchar(100) DEFAULT NULL, - `created` timestamp , - `due` timestamp , + `created` timestamp NULL DEFAULT NULL, + `due` timestamp NULL DEFAULT NULL, `taskMode2` varchar(100) DEFAULT NULL, `taskMode3` varchar(100) DEFAULT NULL, `taskMode` varchar(100) DEFAULT NULL @@ -135,17 +146,14 @@ CREATE TABLE `users` ( `name` varchar(100) NOT NULL, `password` varchar(200) NOT NULL, `email` varchar(255) NOT NULL, - `rocketChatUserId` varchar(400), - `rocketChatPersonalAccessToken` varchar(400), - `rocketChatUsername` varchar(400), - `rocketChatAuthToken` varchar(800), + `rocketChatUserName` varchar(400), `isStudent` tinyint(1) DEFAULT '1' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `workrating` ( - `projectName` varchar(400) NOT NULL, - `userName` varchar(400) NOT NULL, - `fromPeer` varchar(400) NOT NULL, + `projectName` varchar(200) NOT NULL, + `userEmail` varchar(100) NOT NULL, + `fromPeer` varchar(100) NOT NULL, `responsibility` int(11) NOT NULL, `partOfWork` int(11) NOT NULL, `cooperation` int(11) NOT NULL, @@ -153,26 +161,53 @@ CREATE TABLE `workrating` ( `autonomous` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE submissionuser ( submissionId varchar(400), userEmail varchar(255) ); - ALTER TABLE `annotations` - ADD PRIMARY KEY (`id`); + ADD PRIMARY KEY (`id`), + ADD KEY `annotations_fullsubmissions_id_fk` (`targetId`); + +ALTER TABLE `answeredquiz` + ADD KEY `answeredquiz_projects_name_fk` (`projectName`); + +ALTER TABLE `assessmentmechanismselected` + ADD KEY `assessmentmechanismselected_projects_name_fk` (`projectName`); + +ALTER TABLE `categoriesselected` + ADD KEY `categoriesselected_projects_name_fk` (`projectName`); + +ALTER TABLE `contributionrating` + ADD UNIQUE KEY `contributionrating_projectName_userName_fromPeer_uindex` (`projectName`,`userName`,`fromPeer`); ALTER TABLE `fullsubmissions` - ADD PRIMARY KEY (`id`); + ADD PRIMARY KEY (`id`), + ADD KEY `fullsubmissions_projects_name_fk` (`projectName`); + +ALTER TABLE `grades` + ADD UNIQUE KEY `grades_projectName_userName_uindex` (`projectName`,`userName`); + +ALTER TABLE `groupfindingmechanismselected` + ADD KEY `groupfindingmechanismselected_projects_name_fk` (`projectName`); ALTER TABLE `groups` - ADD PRIMARY KEY (`id`); + ADD PRIMARY KEY (`id`), + ADD KEY `groups_projects_name_fk` (`projectName`); ALTER TABLE `groupuser` ADD KEY `userEmail` (`userEmail`), ADD KEY `groupId` (`groupId`); +ALTER TABLE `journals` + ADD PRIMARY KEY (`id`); + +ALTER TABLE `phasesselected` + ADD KEY `phasesselected_projectName_index` (`projectName`); + ALTER TABLE `projects` ADD UNIQUE KEY `name` (`name`), ADD KEY `author` (`author`); @@ -181,34 +216,152 @@ ALTER TABLE `projectuser` ADD KEY `userEmail` (`userEmail`), ADD KEY `projectName` (`projectName`); +ALTER TABLE `quiz` + ADD KEY `quiz_question_projectName_author_index` (`question`,`projectName`,`author`), + ADD KEY `quiz_projects_name_fk` (`projectName`); + ALTER TABLE `submissionpartbodyelements` ADD PRIMARY KEY (`fullSubmissionId`,`category`,`startCharacter`,`endCharacter`); ALTER TABLE `submissionparts` ADD PRIMARY KEY (`fullSubmissionId`,`category`); +ALTER TABLE `tags` + ADD KEY `tags_projectName_index` (`projectName`); + +ALTER TABLE `tasks` + ADD UNIQUE KEY `tasks_userEmail_projectName_taskName_uindex` (`userEmail`,`projectName`,`taskName`), + ADD KEY `tasks_projects_name_fk` (`projectName`); + ALTER TABLE `users` ADD UNIQUE KEY `email` (`email`); +ALTER TABLE `workrating` + ADD UNIQUE KEY `workrating_projectName_userEmail_fromPeer_uindex` (`projectName`,`userEmail`,`fromPeer`); + ALTER TABLE `groups` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + + +ALTER TABLE `annotations` + ADD CONSTRAINT `annotations_fullsubmissions_id_fk` FOREIGN KEY (`targetId`) REFERENCES `fullsubmissions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `answeredquiz` + ADD CONSTRAINT `answeredquiz_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE `assessmentmechanismselected` + ADD CONSTRAINT `assessmentmechanismselected_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `categoriesselected` + ADD CONSTRAINT `categoriesselected_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `contributionrating` + ADD CONSTRAINT `contributionrating_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `fullsubmissions` + ADD CONSTRAINT `fullsubmissions_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `grades` + ADD CONSTRAINT `grades_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `groupfindingmechanismselected` + ADD CONSTRAINT `groupfindingmechanismselected_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `groups` + ADD CONSTRAINT `groups_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE `groupuser` - ADD CONSTRAINT `groupuser_ibfk_1` FOREIGN KEY (`userEmail`) REFERENCES `users` (`email`), - ADD CONSTRAINT `groupuser_ibfk_2` FOREIGN KEY (`groupId`) REFERENCES `groups` (`id`); + ADD CONSTRAINT `groupuser_ibfk_1` FOREIGN KEY (`userEmail`) REFERENCES `users` (`email`) ON DELETE CASCADE, + ADD CONSTRAINT `groupuser_ibfk_2` FOREIGN KEY (`groupId`) REFERENCES `groups` (`id`) ON DELETE CASCADE; + +ALTER TABLE `phasesselected` + ADD CONSTRAINT `phasesselected_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE `projects` ADD CONSTRAINT `projects_ibfk_1` FOREIGN KEY (`author`) REFERENCES `users` (`email`); ALTER TABLE `projectuser` - ADD CONSTRAINT `projectuser_ibfk_1` FOREIGN KEY (`userEmail`) REFERENCES `users` (`email`), - ADD CONSTRAINT `projectuser_ibfk_2` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`); + ADD CONSTRAINT `projectuser_ibfk_1` FOREIGN KEY (`userEmail`) REFERENCES `users` (`email`) ON DELETE CASCADE, + ADD CONSTRAINT `projectuser_ibfk_2` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE; + +ALTER TABLE `quiz` + ADD CONSTRAINT `quiz_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `tasks` + ADD CONSTRAINT `tasks_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `workrating` + ADD CONSTRAINT `workrating_projects_name_fk` FOREIGN KEY (`projectName`) REFERENCES `projects` (`name`) ON DELETE CASCADE ON UPDATE CASCADE; COMMIT; CREATE UNIQUE INDEX fullsubmissions_user_projectName_uindex ON fullsubmissions (user, projectName); -CREATE UNIQUE INDEX tasks_userEmail_projectName_taskName_uindex ON tasks (userEmail, projectName, taskName); +CREATE TABLE profilequestions +( + id int PRIMARY KEY AUTO_INCREMENT, + scaleSize int, + question varchar(500) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE profilequestionoptions +( + id int PRIMARY KEY AUTO_INCREMENT, + profileQuestionId int, + name varchar (255) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE profilequestionanswer +( + profileQuestionId int, + answerIndex int, + selectedAnswer varchar(255) not null, + userEmail varchar(255) not null, + CONSTRAINT profilequestionanswer_profilequestions_id_fk FOREIGN KEY (profileQuestionId) + REFERENCES profilequestions(id), + CONSTRAINT profilequestionanswer_user_email_fk FOREIGN KEY (userEmail) + REFERENCES users(email) +)ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE profilequestionrelations +( + firstQuestionId int, + secondQuestionId int, + relation varchar(200), + CONSTRAINT profilequestionrelations_profilequestions_id_fk FOREIGN KEY (firstQuestionId) REFERENCES profilequestions (id), + CONSTRAINT profilequestionrelations_profilequestions2_id_fk FOREIGN KEY (secondQuestionId) REFERENCES profilequestions (id) +); + +ALTER TABLE profilequestionrelations COMMENT = 'This indicates if a profile question leads to homogenity in groups'; +ALTER TABLE annotations COMMENT = 'Stores comments to a part of the dossier for a category such as RESEARCH'; +ALTER TABLE answeredquiz COMMENT = 'The answered quiz table holds the interpreted result of the quiz answer'; +ALTER TABLE assessmentmechanismselected COMMENT = 'Holds the peer assessement mechanism selected'; +ALTER TABLE categoriesselected COMMENT = 'NOT IMPLEMENTED'; +ALTER TABLE contributionrating COMMENT = 'TODO @Axel plz comment'; +ALTER TABLE fullsubmissions COMMENT = 'This holds the aggregated text of the dossier students should upload'; +ALTER TABLE grades COMMENT = 'Shows the grades that are calculated for a given student'; +ALTER TABLE groupfindingmechanismselected COMMENT = 'Groupfinding is done either automatically or manual'; +ALTER TABLE groups COMMENT = 'the groups that are created'; +ALTER TABLE groupuser COMMENT = 'n x m table for group and user'; +ALTER TABLE phasesselected COMMENT = 'the phase that is selected out of Phase enum'; +ALTER TABLE profilequestionanswer COMMENT = 'the answer to a profile question needed for group finding algorithm'; +ALTER TABLE profilequestionoptions COMMENT = 'the options for a profile question for thegroup finding algorithm'; +ALTER TABLE profilequestions COMMENT = 'stores the questions needed for group finding'; +ALTER TABLE projects COMMENT = 'just a list of all the projects'; +ALTER TABLE projectuser COMMENT = 'n x m for projects and user'; +ALTER TABLE quiz COMMENT = 'lists the quizzes for the app'; +ALTER TABLE submissionpartbodyelements COMMENT = 'holds the parts of a dossier that are annoated with category'; +ALTER TABLE submissionparts COMMENT = 'no idea what that it does but it is important'; +ALTER TABLE submissionuser COMMENT = 'no idea if that is needed. seems not be used'; +ALTER TABLE tags COMMENT = 'lists some tags for the project in order to make it more searchable and for groupfinding'; +ALTER TABLE tasks COMMENT = 'The task table is important. It lists the actual state of the system associated to tasks'; +ALTER TABLE users COMMENT = 'Just lists the users'; +ALTER TABLE workrating COMMENT = '@Axel plz comment'; + + + + + + diff --git a/gemeinsamforschen/src/test/resources/database/testproject.sql b/gemeinsamforschen/src/test/resources/database/testproject.sql index c15d7e9c205f8a458ebf0134a305c3dceec22242..102f33a3197fa84e81a575178f9fbd2d6364735a 100644 --- a/gemeinsamforschen/src/test/resources/database/testproject.sql +++ b/gemeinsamforschen/src/test/resources/database/testproject.sql @@ -1,8 +1,8 @@ -INSERT IGNORE INTO projects (name, password, active, timecreated, author, adminPassword, phase) VALUES ("test1", "", 1, -current_timestamp, "vodka@yolo.com", "", "GroupFormation" ); +INSERT IGNORE INTO projects (name, password, active, timecreated, author, phase) VALUES ("test1", "", 1, +current_timestamp, "vodka@yolo.com", "GroupFormation" ); -INSERT IGNORE INTO projects (name, password, active, timecreated, author, adminPassword, phase) VALUES ("test2", "", 1, -current_timestamp, "vodka@yolo.com", "", "GroupFormation" ); +INSERT IGNORE INTO projects (name, password, active, timecreated, author, phase) VALUES ("test2", "", 1, +current_timestamp, "vodka@yolo.com", "GroupFormation" ); insert IGNORE into projectuser (projectName, userEmail) VALUES ("test1", "vodkas@yolo.com"); insert IGNORE into projectuser (projectName, userEmail) VALUES ("test1", "vodkass@yolo.com");