diff --git a/distro/LICENSE b/distro/LICENSE
new file mode 100644
index 0000000..2d9616a
--- /dev/null
+++ b/distro/LICENSE
@@ -0,0 +1 @@
+${gcube.license}
\ No newline at end of file
diff --git a/distro/README b/distro/README
new file mode 100644
index 0000000..640cbf5
--- /dev/null
+++ b/distro/README
@@ -0,0 +1,65 @@
+The gCube System - ${name}
+--------------------------------------------------
+
+${description}
+
+
+${gcube.description}
+
+${gcube.funding}
+
+
+Version
+--------------------------------------------------
+
+${version} (${buildDate})
+
+Please see the file named "changelog.xml" in this directory for the release notes.
+
+
+Authors
+--------------------------------------------------
+
+* Panagiota Koltsida (p.koltsida@di.uoa.gr), University of Athens
+
+
+Maintainers
+-----------
+
+* Panagiota Koltsida (p.koltsida@di.uoa.gr), University of Athens
+
+
+Download information
+--------------------------------------------------
+
+Source code is available from SVN:
+ ${scm.url}
+
+Binaries can be downloaded from the gCube website:
+ ${gcube.website}
+
+
+Installation
+--------------------------------------------------
+
+Use the respective war
+
+
+Documentation
+--------------------------------------------------
+
+Documentation is available on-line in the gCube Wiki:
+ ${gcube.wikiRoot}/Users%27_Management
+
+
+Support
+--------------------------------------------------
+
+Bugs and support requests can be reported in the gCube issue tracking tool:
+ ${gcube.issueTracking}
+
+
+Licensing
+--------------------------------------------------
+
+This software is licensed under the terms you may find in the file named "LICENSE" in this directory.
\ No newline at end of file
diff --git a/distro/changelog.xml b/distro/changelog.xml
new file mode 100644
index 0000000..5c54457
--- /dev/null
+++ b/distro/changelog.xml
@@ -0,0 +1,33 @@
+
+
+ First release of the redesigned portlet
+
+
+ UI usability improvements
+ Added more options in handling users and/or groups/roles
+ Fixed minor bugs concerning user deletion from VRE and scroll bars in modals
+
+
+ VRE Managers are informed for acceptance or rejection of requests
+ Fixed a bug in the request's grid causing grid not to be refreshed after an action
+
+
+ Displaying rejected requests
+ Improved modals' labels
+
+
+ Fixed a scroll bar issue
+ Updated the label for unregistering users from a VRE
+ Fixed a bug which preventing the display of the requests for registration
+
+
+ Fixed date sorting in table
+ Added notification for group membership
+
+
+ Removed acceptance admin column in table
+
+
+ Email on role assignment/revoke
+
+
diff --git a/distro/descriptor.xml b/distro/descriptor.xml
new file mode 100644
index 0000000..d98989d
--- /dev/null
+++ b/distro/descriptor.xml
@@ -0,0 +1,31 @@
+
+ servicearchive
+
+ tar.gz
+
+ /
+
+
+ ${distroDirectory}
+ /
+ true
+
+ README
+ LICENSE
+ changelog.xml
+
+ 755
+ true
+
+
+
+
+ target/${build.finalName}.${project.packaging}
+ /${artifactId}
+
+
+
+
\ No newline at end of file
diff --git a/distro/profile.xml b/distro/profile.xml
new file mode 100644
index 0000000..739b0ac
--- /dev/null
+++ b/distro/profile.xml
@@ -0,0 +1,25 @@
+
+
+
+ Service
+
+ ${description}
+ PortletAdmin
+ ${artifactId}
+ ${version}
+
+
+ ${artifactId}
+ ${version}
+
+ ${groupId}
+ ${artifactId}
+ ${version}
+
+
+ target/${build.finalName}.war
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..bb87de6
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,162 @@
+
+
+
+ 4.0.0
+
+
+ maven-parent
+ org.gcube.tools
+ 1.0.0
+
+
+ org.gcube.portlets.admin
+ UsersManagementPortlet
+ war
+ UsersManagement Portlet
+ UsersManagementPortlet portlet
+ 3.8.1-SNAPSHOT
+
+
+ scm:svn:https://svn.madgik.di.uoa.gr/code/gcube/Portlets/UsersManagementPortlet
+ scm:svn:https://svn.madgik.di.uoa.gr/code/gcube/Portlets/UsersManagementPortlet
+ https://svn.madgik.di.uoa.gr/code/gcube/Portlets/UsersManagementPortlet
+
+
+
+ 6.2.2
+ ${project.basedir}/distro
+
+
+
+
+ org.gcube.distribution
+ maven-portal-bom
+ LATEST
+ pom
+ import
+
+
+
+
+
+ com.liferay.portal
+ portal-service
+ ${liferay.version}
+ provided
+
+
+ javax.portlet
+ portlet-api
+ provided
+
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+ provided
+
+
+
+ com.liferay.portal
+ util-taglib
+ 6.2.3
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.18
+
+
+
+
+
+ jstl
+ jstl
+ 1.2
+
+
+ com.liferay.maven.archetypes
+ liferay-portlet-archetype
+ ${liferay.version}
+
+
+
+ com.google.code.gson
+ gson
+ 2.3.1
+
+
+ com.google.guava
+ guava
+
+
+ com.sun.mail
+ javax.mail
+ provided
+
+
+ org.gcube.dvos
+ usermanagement-core
+
+ provided
+
+
+ org.gcube.common.portal
+ portal-manager
+
+ provided
+
+
+ org.gcube.portal
+ custom-portal-handler
+
+ provided
+
+
+ org.gcube.portal.mailing
+ email-templates-library
+ [1.0.0-SNAPSHOT,2.0.0-SNAPSHOT)
+
+
+
+
+
+
+ maven-compiler-plugin
+ 2.5
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+ maven-resources-plugin
+ 2.5
+
+ UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.6
+
+
+ ${distroDirectory}/descriptor.xml
+
+
+
+
+ servicearchive
+ install
+
+ single
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/EmailPartsConstruction.java b/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/EmailPartsConstruction.java
new file mode 100644
index 0000000..5e04e55
--- /dev/null
+++ b/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/EmailPartsConstruction.java
@@ -0,0 +1,200 @@
+/**
+ *
+ */
+package gr.cite.bluebridge.portlets.admin.usersmanagementportlet;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Properties;
+
+import com.liferay.portal.kernel.log.Log;
+import com.liferay.portal.kernel.log.LogFactoryUtil;
+
+/**
+ * @author Panagiota Koltsida
+ *
+ */
+public class EmailPartsConstruction {
+ static String gCubeFileName = "gcube-data.properties";
+ static String propFileName = "EmailTemplates.properties";
+ static String catalinaHome = System.getProperty("catalina.base");
+ static String filePath = catalinaHome + File.separator + "conf" + File.separator;
+
+ private static Log logger = LogFactoryUtil.getLog(EmailPartsConstruction.class);
+
+ public static String returnProperty(String bodyKey){
+ InputStream is = null;
+ try{
+ Properties props = new Properties();
+ logger.debug("Using file -> " + filePath + propFileName);
+ is = new FileInputStream(filePath + propFileName);
+
+ if (is != null) {
+ props.load(is);
+ }
+
+ String prop1 = props.getProperty(bodyKey);
+ logger.debug("The body msg that will be sent is.... ");
+ logger.debug(prop1);
+ return prop1;
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public static String returnPortalName(){
+ InputStream is = null;
+ try{
+ Properties props = new Properties();
+ logger.debug("Using file -> " + filePath + gCubeFileName);
+ is = new FileInputStream(filePath + gCubeFileName);
+
+ if (is != null) {
+ props.load(is);
+ }
+
+ String prop1 = props.getProperty("portalinstancename");
+ logger.debug(prop1);
+ return prop1;
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public static String returnBodyForMembershipRequestApproval(){
+ InputStream is = null;
+ String body = "";
+ try{
+ Properties props = new Properties();
+ logger.debug("Using file -> " + filePath + propFileName);
+ is = new FileInputStream(filePath + propFileName);
+
+ if (is != null) {
+ props.load(is);
+ }
+
+ String prop1 = props.getProperty(body);
+ logger.debug("The body msg that will be sent is.... ");
+ logger.debug(prop1);
+ return prop1;
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public static void sendDismissalEmailToUser(){
+ InputStream is = null;
+ try{
+ Properties props = new Properties();
+ logger.debug("Using file -> " + filePath + propFileName);
+ is = new FileInputStream(filePath + propFileName);
+
+ if (is != null) {
+ props.load(is);
+ }
+
+ String prop1 = props.getProperty("prop1");
+ System.out.println(prop1);
+
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+ }
+
+ public static String sendMailForMembershipRequestAcceptanceOrRejection(
+ String acceptRejectBody,
+ String userFullName, String groupName, String managerFullName,
+ String managerEmail, String userEmail, String portalComplete){
+
+ String body = returnProperty(acceptRejectBody);
+
+ if(body != null){
+ logger.info("portal name: " +portalComplete);
+ return java.text.MessageFormat.format(body, userFullName, groupName, managerFullName, managerEmail, userEmail, portalComplete);
+ }
+
+ return "";
+ }
+
+ public static void sendMailForMembershipRequestRejection(
+ String userFullName, String groupName, String managerFullName,
+ String managerEmail, String userEmail, String portalComplete){
+
+ String body = returnProperty("membershipRequestRejectionBody");
+
+ if(body != null){
+ java.text.MessageFormat.format(body, userFullName, groupName, managerFullName, managerEmail, userEmail);
+ }
+ }
+
+ public static String subjectForMembershipRequestAcceptanceOrRejection(
+ String subjectProperty, String groupName){
+
+ String subject = returnProperty(subjectProperty);
+ if(subject != null){
+ String finalString = groupName;
+ return java.text.MessageFormat.format(subject, finalString);
+ }
+
+ return "";
+ }
+
+ public static String subjectForUserDismissalFromSite(
+ String subjectProperty, String groupName){
+ return subjectForMembershipRequestAcceptanceOrRejection(subjectProperty, groupName);
+ }
+
+ public static void userDismissalFromSite(String groupName){
+ String body = returnProperty("userDismissalFromSiteBody");
+
+ if(body != null){
+ java.text.MessageFormat.format(body, groupName);
+ }
+ }
+
+ public static String getSiteTeamAssignmentSubject(String siteTeamName) {
+ String subject = returnProperty("siteTeamAssignmentSubject");
+ String response = "";
+
+ if(subject != null && !subject.isEmpty()) {
+ response = java.text.MessageFormat.format(subject, siteTeamName);
+ }
+
+ return response;
+ }
+
+ public static String getSiteTeamDismissalSubject(String siteTeamName) {
+ String subject = returnProperty("siteTeamDismissalSubject");
+ String response = "";
+
+ if(subject != null && !subject.isEmpty()) {
+ response = java.text.MessageFormat.format(subject, siteTeamName);
+ }
+
+ return response;
+ }
+
+ public static String getRoleAssignmentRevokeSubject(String groupname) {
+ String subject = returnProperty("roleAssignmentRevokeSubject");
+ String response = "";
+
+ if(subject != null && !subject.isEmpty()) {
+ response = java.text.MessageFormat.format(subject, groupname);
+ }
+
+ return response;
+ }
+
+ public static String constructPortal(String protocol, String port, String baseURL){
+
+
+ return baseURL;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/UsersManagementPortletHome.java b/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/UsersManagementPortletHome.java
new file mode 100644
index 0000000..afd5cb0
--- /dev/null
+++ b/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/UsersManagementPortletHome.java
@@ -0,0 +1,1443 @@
+package gr.cite.bluebridge.portlets.admin.usersmanagementportlet;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+import javax.portlet.GenericPortlet;
+import javax.portlet.PortletException;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletRequestDispatcher;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+import javax.portlet.ResourceRequest;
+import javax.portlet.ResourceResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import gr.cite.bluebridge.portlets.admin.usersmanagementportlet.mail.templates.TemplateUserRolesModifiedForGroup;
+import org.gcube.common.portal.PortalContext;
+import org.gcube.portal.mailing.service.EmailTemplateService;
+import org.gcube.portal.mailing.templates.TemplateUserApprovedRequestVRE;
+import org.gcube.portal.mailing.templates.TemplateUserHasBeenUnregisteredVRE;
+import org.gcube.portal.mailing.templates.TemplateUserRejectedRequestVRE;
+import org.gcube.portal.mailing.message.EmailAddress;
+import org.gcube.portal.mailing.message.Recipient;
+import org.gcube.portal.mailing.message.RecipientType;
+import org.gcube.vomanagement.usermanagement.GroupManager;
+import org.gcube.vomanagement.usermanagement.RoleManager;
+import org.gcube.vomanagement.usermanagement.UserManager;
+import org.gcube.vomanagement.usermanagement.exception.GroupRetrievalFault;
+import org.gcube.vomanagement.usermanagement.exception.RoleRetrievalFault;
+import org.gcube.vomanagement.usermanagement.exception.TeamRetrievalFault;
+import org.gcube.vomanagement.usermanagement.exception.UserManagementPortalException;
+import org.gcube.vomanagement.usermanagement.exception.UserManagementSystemException;
+import org.gcube.vomanagement.usermanagement.exception.UserRetrievalFault;
+import org.gcube.vomanagement.usermanagement.impl.LiferayGroupManager;
+import org.gcube.vomanagement.usermanagement.impl.LiferayRoleManager;
+import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager;
+import org.gcube.vomanagement.usermanagement.model.GCubeGroup;
+import org.gcube.vomanagement.usermanagement.model.GCubeMembershipRequest;
+import org.gcube.vomanagement.usermanagement.model.GCubeRole;
+import org.gcube.vomanagement.usermanagement.model.GCubeTeam;
+import org.gcube.vomanagement.usermanagement.model.GCubeUser;
+import org.gcube.vomanagement.usermanagement.model.MembershipRequestStatus;
+
+import com.google.gson.Gson;
+import com.liferay.portal.kernel.exception.PortalException;
+import com.liferay.portal.kernel.exception.SystemException;
+import com.liferay.portal.kernel.json.JSONArray;
+import com.liferay.portal.kernel.json.JSONFactoryUtil;
+import com.liferay.portal.kernel.json.JSONObject;
+import com.liferay.portal.kernel.log.Log;
+import com.liferay.portal.kernel.log.LogFactoryUtil;
+import com.liferay.portal.kernel.util.ParamUtil;
+import com.liferay.portal.util.PortalUtil;
+
+import gr.cite.bluebridge.portlets.admin.usersmanagementportlet.mail.templates.TemplateUserHasBeenAssociatedWithGCubeTeam;
+import gr.cite.bluebridge.portlets.admin.usersmanagementportlet.mail.templates.TemplateUserHasBeenDissAssociatedWithGCubeTeam;
+
+public class UsersManagementPortletHome extends GenericPortlet {
+ protected String viewTemplate;
+ protected boolean once = true;
+ public GCubeUser currentUser;
+ private static Log _log = LogFactoryUtil.getLog(UsersManagementPortletHome.class);
+ private static final int REJECT_MEMBERSHIP_REQUESTS_TABLE = 0;
+ private static final int APPROVE_MEMBERSHIP_REQUESTS_TABLE = 1;
+ private static final int REFRESH_MEMBERSHIP_REQUESTS_TABLE = 2;
+ private static final int EDIT_CURRENT_USERS_TABLE= 1;
+ private static final int REFRESH_CURRENT_USERS_TABLE = 2;
+ private static final int SITE_TEAMS_TABLE_CREATE_GROUP = 3;
+ private static final int REFRESH_SITE_TEAMS_TABLE = 2;
+ private static final int EDIT_SITE_TEAMS_TABLE = 1;
+ private static final int DELETE_SITE_TEAMS_TABLE = 0;
+ private static final int MASS_EDIT_USERS = 0;
+ private static final int ASSIGN_ROLES_TO_USERS = 1;
+ private static final int ASSIGN_TEAMS_TO_USERS = 2;
+
+ public void init() {
+ viewTemplate = getInitParameter("view-template");
+ }
+
+ public void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException, PortletException {
+ include(viewTemplate, renderRequest, renderResponse);
+ }
+
+ @Override
+ public void serveResource(ResourceRequest request, ResourceResponse response) throws PortletException, IOException {
+ PortalContext pContext = PortalContext.getConfiguration();
+ HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest(request);
+
+ currentUser = pContext.getCurrentUser(httpServletRequest);
+
+ PortletRequest portletRequest = (PortletRequest)request.getAttribute("javax.portlet.request");
+ JSONObject jsonObject = JSONFactoryUtil.createJSONObject();
+ long selfId = currentUser.getUserId();
+
+ //CurrentUsers
+ boolean currentUsersTable = ParamUtil.getBoolean(request, "currentUsersTable");
+ boolean deleteUsersFromCurrentUsersTable = ParamUtil.getBoolean(request, "deleteUsersFromCurrentUsersTable");
+ int modeCurrentUsersTable = ParamUtil.getInteger(request, "modeCurrentUsersTable");
+ long groupId = pContext.getCurrentGroupId(httpServletRequest);
+
+ currentUsersTableSection(
+ currentUsersTable, deleteUsersFromCurrentUsersTable,modeCurrentUsersTable,
+ groupId, jsonObject, portletRequest, request, selfId);
+
+ //MembershipRequests
+ boolean fetchUsersRequests = ParamUtil.getBoolean(request, "fetchUsersRequests");
+ int modeMembershipRequestsTable = ParamUtil.getInteger(request, "modeMembershipRequestsTable");
+
+ membershipRequestsSection(
+ fetchUsersRequests, modeMembershipRequestsTable,
+ jsonObject, request, portletRequest,
+ groupId, httpServletRequest);
+
+ boolean userRequestRejectionEmailSubject = ParamUtil.getBoolean(request, "userRequestRejectionEmailSubject");
+ if(userRequestRejectionEmailSubject){
+ try {
+ GroupManager gm = new LiferayGroupManager();
+ String groupName = gm.getGroup(groupId).getGroupName();
+
+ String gatewayNameForSubject = PortalContext.getConfiguration().getGatewayName(httpServletRequest);
+ String groupNameForSubject = "";
+ if(gm.isRootVO(groupId)) {
+ groupNameForSubject += " Virtual Organization";
+ groupName = "" + groupName + " Virtual Organization";
+ } else if(gm.isVRE(groupId)) {
+ groupNameForSubject += " Virtual Research Environment";
+ groupName = "" + groupName + " Virtual Research Environment";
+ }else{
+ groupNameForSubject = groupName;
+ _log.debug("isRootVO: " + gm.isRootVO(groupId) + "\nisVRE: " + gm.isVRE(groupId));
+ }
+ String emailSubject = getUserRequestRejectionEmailSubject(gatewayNameForSubject, groupNameForSubject);
+ jsonObject.put("userRequestRejectionEmailSubject", emailSubject);
+
+ ArrayList managersEmails = getVREManagersEmailsForGroup(groupId);
+ String json = new Gson().toJson(managersEmails );
+ jsonObject.put("userRequestRejectionEmailAdminsMailsCC", json);
+
+ _log.debug("VRE Manager rejection request email subject -> " + emailSubject);
+ } catch (UserManagementSystemException | GroupRetrievalFault e) {
+ _log.error("Failed to retrieve rejection request email subject");
+ e.printStackTrace();
+ }
+ }
+
+ //RejectedmembershipRequests
+ boolean fetchUsersRejectedRequests = ParamUtil.getBoolean(request, "fetchUsersRejectedRequests");
+
+ rejectedMembershipRequestsSection(
+ fetchUsersRejectedRequests,
+ jsonObject, request, portletRequest,
+ groupId, httpServletRequest);
+
+ //count the membership requests
+ boolean countUsersMembershipRequests = ParamUtil.getBoolean(request, "countUsersMembershipRequests");
+ countUsersMembershipRequestsSection(countUsersMembershipRequests, jsonObject, groupId);
+
+ //Site-teams handling
+ boolean fetchAllSiteTeamsForTheCurrentGroup = ParamUtil.getBoolean(request, "fetchAllSiteTeamsForTheCurrentGroup");
+ int modeSiteTeams = ParamUtil.getInteger(request, "modeSiteTeams");
+ if(fetchAllSiteTeamsForTheCurrentGroup){
+ try {
+ siteTeamsForTheCurrentGroupSection( request, selfId, modeSiteTeams, groupId, jsonObject);
+ } catch (SystemException e) {
+ e.printStackTrace();
+ }
+ }
+
+ boolean rolesInitial = ParamUtil.getBoolean(request, "rolesInitial");
+ if(rolesInitial) {
+ fetchRolesNames(groupId, jsonObject);
+ }
+
+ boolean teamsInitial = ParamUtil.getBoolean(request, "teamsInitial");
+ if(teamsInitial) {
+ try {
+ fetchTeamsNames(groupId, jsonObject);
+ } catch (GroupRetrievalFault e) {
+ e.printStackTrace();
+ }
+ }
+
+ response.getWriter().println(jsonObject);
+ super.serveResource(request, response);
+ }
+
+ protected void include(String path, RenderRequest renderRequest, RenderResponse renderResponse) throws IOException,PortletException {
+ String url = null;
+ if (renderRequest.getParameter("jspPage") == null || renderRequest.getParameter("jspPage").equals("./"))
+ url = path + "usersManagement.jsp";
+ else
+ url = path + renderRequest.getParameter("jspPage");
+
+ PortletRequestDispatcher portletRequestDispatcher = getPortletContext().getRequestDispatcher(url);
+
+ if (portletRequestDispatcher == null) {
+ _log.error(path + " is not a valid include");
+ }
+ else {
+ portletRequestDispatcher.include(renderRequest, renderResponse);
+ }
+ }
+
+ protected void rejectMembershipRequests(
+ PortletRequest portletRequest,
+ ResourceRequest request, long[] theReqIDs,
+ long groupId, long managerId, boolean CustomRejectionEmailFromAdmin,
+ String CustomRejectionEmailBodyFromAdmin)
+ throws NumberFormatException, SystemException, PortalException, UserManagementSystemException, GroupRetrievalFault{
+ UserManager um = new LiferayUserManager();
+ List reqs;
+ GroupManager gm = new LiferayGroupManager();
+ String managerName = currentUser.getUsername();
+ String groupName = gm.getGroup(groupId).getGroupName();
+ String groupNameForSubject = "";
+
+ if(gm.isRootVO(groupId)) {
+ groupNameForSubject += " Virtual Organization";
+ groupName = "" + groupName + " Virtual Organization";
+ } else if(gm.isVRE(groupId)) {
+ groupNameForSubject += " Virtual Research Environment";
+ groupName = "" + groupName + " Virtual Research Environment";
+ }else{
+ groupNameForSubject = groupName;
+ _log.debug("isRootVO: " + gm.isRootVO(groupId) + "\nisVRE: " + gm.isVRE(groupId));
+ }
+
+ HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest(request);
+
+ _log.debug("Rejecting requests for the group: " + groupName);
+
+ try {
+ reqs = um.listMembershipRequestsByGroup(groupId);
+ ArrayList managersEmails = getVREManagersEmailsForGroup(groupId);
+ for(GCubeMembershipRequest req : reqs){
+ for(long reqId : theReqIDs){
+ if(req.getStatus() == MembershipRequestStatus.REQUEST && req.getMembershipRequestId() == reqId){
+ GCubeUser replyUser = um.getUserById(managerId);
+ String replyComment = "Membership Request rejected";
+ GCubeMembershipRequest gcmr = um.rejectMembershipRequest(req.getRequestingUser().getUserId(), groupId, replyUser.getUsername(), replyComment);
+ String userName = req.getRequestingUser().getUsername();
+
+ String emailRecipient = req.getRequestingUser().getEmail();
+ String emailSubject = EmailPartsConstruction.subjectForMembershipRequestAcceptanceOrRejection(
+ "membershipRequestRejectionSubject", groupNameForSubject);
+
+ // bcc also the VRE managers to be notified about the registration
+ int recSize = managersEmails.size() + 1;
+ Recipient[] recs = new Recipient[recSize];
+ recs[0] = new Recipient(new EmailAddress(emailRecipient), RecipientType.TO);
+ if (!managersEmails.isEmpty()) {
+ int i = 1;
+ for (String mEmail : managersEmails) {
+ recs[i] = new Recipient(new EmailAddress(mEmail), RecipientType.BCC);
+ i++;
+ }
+ }
+
+ TemplateUserRejectedRequestVRE requestRejectedTemplate = new TemplateUserRejectedRequestVRE(
+ req.getRequestingUser(), replyUser, gm.getGroup(groupId), gcmr.getCreateDate(),
+ PortalContext.getConfiguration().getGatewayName(httpServletRequest), PortalContext.getConfiguration().getGatewayURL(httpServletRequest));
+ EmailTemplateService.send(emailSubject, (org.gcube.common.portal.mailing.templates.Template)requestRejectedTemplate, httpServletRequest, recs);
+
+ _log.debug("Admin: " + managerName + " rejected the membership request of the user:" + userName + " for the site: " + groupName);
+ }
+ }
+ }
+ } catch (UserManagementPortalException e) {
+ _log.debug("MembershipRequest retrieval failure");
+ e.printStackTrace();
+ } catch (UserManagementSystemException e) {
+ _log.debug("MembershipRequest retrieval failure");
+ e.printStackTrace();
+ } catch (GroupRetrievalFault e) {
+ _log.debug("MembershipRequest retrieval failure");
+ e.printStackTrace();
+ } catch (UserRetrievalFault e) {
+ _log.debug("MembershipRequest retrieval failure");
+ e.printStackTrace();
+ }
+ }
+
+ protected void acceptMemebershipRequestAndAddUsersToGroup(PortletRequest portletRequest, ResourceRequest request, long[] reqIDs, Long groupId, Long managerId) throws NumberFormatException, PortalException, SystemException, UserManagementSystemException, GroupRetrievalFault, UserRetrievalFault {
+ LiferayUserManager lum = new LiferayUserManager();
+ List membershipRequests = lum.listMembershipRequestsByGroup(groupId);
+ GCubeUser manager = lum.getUserById(managerId);
+ GroupManager gm = new LiferayGroupManager();
+ String groupName = gm.getGroup(groupId).getGroupName();
+ String groupNameForSubject = "";
+ HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest(request);
+
+ if(gm.isRootVO(groupId)) {
+ groupNameForSubject += " Virtual Organization";
+ groupName = "" + groupName + " Virtual Organization";
+ } else if(gm.isVRE(groupId)) {
+ groupNameForSubject += " Virtual Research Environment";
+ groupName = "" + groupName + " Virtual Research Environment";
+ }else{
+ groupNameForSubject = groupName;
+ _log.debug("isRootVO: " + gm.isRootVO(groupId) + "\nisVRE: " + gm.isVRE(groupId));
+ }
+
+ _log.debug("Accepting membership requests for the group: " + groupName);
+ ArrayList managersEmails = getVREManagersEmailsForGroup(groupId);
+ for(GCubeMembershipRequest gcmr : membershipRequests){
+ for(long reqId : reqIDs){
+ if(gcmr.getStatus() == MembershipRequestStatus.REQUEST && gcmr.getMembershipRequestId() == reqId){
+ boolean addUserToGroup = true;
+ try {
+ String managerComments = "Membership request approved";
+ @SuppressWarnings("unused")
+ GCubeMembershipRequest gcmr2 = lum.acceptMembershipRequest(gcmr.getRequestingUser().getUserId(), groupId, addUserToGroup, manager.getUsername(), managerComments);
+ lum.assignUserToGroup(groupId, gcmr.getRequestingUser().getUserId());
+ String userName = gcmr.getRequestingUser().getUsername();
+ String managerName = manager.getUsername();
+
+ _log.debug("Admin: " + managerName + " accepted user's : " + userName + " membership request for the site: " + groupName);
+
+ String userEmail = gcmr.getRequestingUser().getEmail();
+
+ String properEmailSubject = EmailPartsConstruction.subjectForMembershipRequestAcceptanceOrRejection(
+ "membershipRequestAcceptanceSubject", groupNameForSubject);
+
+ // bcc also the VRE managers to be notified about the registration
+ int recSize = managersEmails.size() + 1;
+ Recipient[] recs = new Recipient[recSize];
+ recs[0] = new Recipient(new EmailAddress(userEmail), RecipientType.TO);
+ if (!managersEmails.isEmpty()) {
+ int i = 1;
+ for (String mEmail : managersEmails){
+ recs[i] = new Recipient(new EmailAddress(mEmail), RecipientType.BCC);
+ i++;
+ }
+ }
+
+
+ TemplateUserApprovedRequestVRE requestAcceptedTemplate = new TemplateUserApprovedRequestVRE(
+ gcmr.getRequestingUser(), manager, gm.getGroup(groupId), gcmr.getCreateDate(),
+ PortalContext.getConfiguration().getGatewayName(httpServletRequest), PortalContext.getConfiguration().getGatewayURL(httpServletRequest));
+ EmailTemplateService.send(properEmailSubject, (org.gcube.common.portal.mailing.templates.Template)requestAcceptedTemplate, httpServletRequest, recs);
+ } catch (UserManagementPortalException e) {
+ _log.debug("User: " + gcmr.getRequestingUser().getUsername() + " wasn't added to the site: " + gm.getGroup(groupId).getGroupName());
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+ protected JSONArray numberOfRequestsForSpecificGroup(long groupId) throws PortalException, SystemException, UserManagementSystemException, GroupRetrievalFault, UserRetrievalFault{
+ JSONArray ja = JSONFactoryUtil.createJSONArray();
+ UserManager um = new LiferayUserManager();
+ LiferayGroupManager lgm = new LiferayGroupManager();
+ String groupName = lgm.getGroup(groupId).getGroupName();
+ _log.debug("Counting membership requests for group: " + groupName + " ...");
+
+ List gcmrList = new ArrayList();
+ for(GCubeMembershipRequest gcmr : um.listMembershipRequestsByGroup(groupId)){
+ if(gcmr.getStatus() == MembershipRequestStatus.REQUEST){
+ gcmrList.add(gcmr);
+ }
+ }
+ _log.debug("There are " + gcmrList.size() + " for the group: " + groupName);
+ ja.put(gcmrList.size());
+
+ ja.put(EmailPartsConstruction.returnPortalName());
+
+ return ja;
+ }
+
+ protected JSONArray currentGroupUsers( long groupId, long selfId, HttpServletRequest httpServletRequest) throws GroupRetrievalFault, UserManagementSystemException, UserRetrievalFault{
+ JSONArray ja = JSONFactoryUtil.createJSONArray();
+ List users = new ArrayList();
+ LiferayUserManager lm = new LiferayUserManager();
+ LiferayGroupManager lgm = new LiferayGroupManager();
+ String groupName = lgm.getGroup(groupId).getGroupName();
+
+ try{
+ users = lm.listUsersByGroup(groupId);
+ _log.debug("Retrieving users for the group: " + groupName);
+
+ if(users.size() > 0){
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MMM-yyyy", httpServletRequest.getLocale());
+
+ _log.debug("There are " + users.size() + " users in the group: " + groupName);
+ for(GCubeUser someUser : users){
+ JSONObject jo = JSONFactoryUtil.createJSONObject();
+ try{
+ List gcmrs = lm.getMembershipRequests(someUser.getUserId(), groupId, MembershipRequestStatus.APPROVED);
+ GCubeMembershipRequest mr = gcmrs.get(gcmrs.size()-1);
+
+ jo.put("requestDate", dateFormat.format(mr.getCreateDate()));
+ jo.put("RequestDateObject", mr.getCreateDate());
+ jo.put("validationDate", dateFormat.format(mr.getReplyDate()));
+ jo.put("ValidationDateObject", mr.getReplyDate());
+ jo.put("requestComments", mr.getComment());
+ jo.put("reqID", mr.getMembershipRequestId());
+ }catch(Exception e){
+ jo.put("requestDate", "-");
+ jo.put("RequestDateObject", "-");
+ jo.put("validationDate", "-");
+ jo.put("ValidationDateObject", "-");
+ jo.put("requestComments", "-");
+ jo.put("reqID", 0);
+ }
+
+ jo.put("userName", someUser.getUsername());
+ jo.put("userFullName", someUser.getFullname());
+ jo.put("userEmail", someUser.getEmail());
+ jo.put("userId", someUser.getUserId());
+ String isSelf = (someUser.getUserId() == selfId) ? "true" : "false";
+ jo.put("isSelf", isSelf);
+
+ LiferayRoleManager lrm = new LiferayRoleManager();
+ List rolesList = lrm.listRolesByUserAndGroup(someUser.getUserId(), groupId);
+ JSONArray ja2 = JSONFactoryUtil.createJSONArray();
+ for(GCubeRole r : rolesList){
+ ja2.put(r.getRoleName());
+ }
+
+ jo.put("userSiteRoles", ja2);
+
+ List gcubeTeams = lrm.listTeamsByUserAndGroup(someUser.getUserId(), groupId);
+ JSONArray ja3 = JSONFactoryUtil.createJSONArray();
+ for(GCubeTeam t : gcubeTeams){
+ ja3.put(t.getTeamName());
+ }
+
+ jo.put("userTeams", ja3);
+
+ ja.put(jo);
+ }
+ _log.debug("User: " + currentUser.getUsername() + " is displaying users of the site: " + lgm.getGroup(groupId).getGroupName());
+ }
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+ return ja;
+ }
+
+ protected JSONArray currentGroupUsersRequests(long groupId, HttpServletRequest httpServletRequest) throws GroupRetrievalFault, UserManagementSystemException, UserRetrievalFault{
+ JSONArray ja = JSONFactoryUtil.createJSONArray();
+ @SuppressWarnings("unused")
+ List users = new ArrayList();
+ LiferayUserManager lm = new LiferayUserManager();
+ users = lm.listUsersByGroup(groupId);
+ LiferayGroupManager lgm = new LiferayGroupManager();
+ String groupName = lgm.getGroup(groupId).getGroupName();
+
+ _log.debug("Retrieving requests for: " + groupName);
+ List reqs = new ArrayList();
+ try{
+ reqs = lm.listMembershipRequestsByGroup(groupId);
+ _log.debug(reqs.size() + " requests for: " + groupName);
+ if(reqs.size() > 0) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MMM-yyyy", httpServletRequest.getLocale());
+
+ for(GCubeMembershipRequest gcmr : reqs){
+ if(gcmr.getStatus() == MembershipRequestStatus.REQUEST){
+ JSONObject jo = JSONFactoryUtil.createJSONObject();
+ jo.put("requestDate", dateFormat.format(gcmr.getCreateDate()));
+ jo.put("requestDateObject", gcmr.getCreateDate());
+ try {
+ jo.put("validationDate", dateFormat.format(gcmr.getReplyDate()));
+ } catch(Exception e) {
+ _log.debug("Reply date doesn\'t exist for the membership request of user: " + gcmr.getRequestingUser().getUsername());
+ }
+ jo.put("requestComments", gcmr.getComment());
+ jo.put("requestId", gcmr.getMembershipRequestId());
+ GCubeUser gcu = gcmr.getRequestingUser();
+ jo.put("userName", gcu.getUsername());
+ jo.put("userFullName", gcu.getFullname());
+ jo.put("userEmail", gcu.getEmail());
+ jo.put("userId", gcu.getUserId());
+ LiferayRoleManager lrm = new LiferayRoleManager();
+ List rolesList = lrm.listRolesByUserAndGroup(gcu.getUserId(), groupId);
+ List roles = new ArrayList();
+ for(GCubeRole gcr : rolesList){
+ roles.add(gcr.getRoleName());
+ }
+ jo.put("userSiteRoles", roles.toString());
+ ja.put(jo);
+ }
+
+ }
+ }
+ _log.debug("The admin: " + currentUser.getUsername() + " displayed membershipRequests for the site: " + groupName);
+ }catch(Exception e){
+ _log.debug("Error while retrieving requests for: " + groupName);
+ e.printStackTrace();
+ }
+ return ja;
+ }
+
+ protected JSONArray currentGroupRejectedUsersRequests( long groupId, HttpServletRequest httpServletRequest) throws GroupRetrievalFault, UserManagementSystemException, UserRetrievalFault{
+ JSONArray ja = JSONFactoryUtil.createJSONArray();
+ @SuppressWarnings("unused")
+ List users = new ArrayList();
+ LiferayUserManager lm = new LiferayUserManager();
+ users = lm.listUsersByGroup(groupId);
+ LiferayGroupManager lgm = new LiferayGroupManager();
+
+ List reqs = new ArrayList();
+ String groupName = lgm.getGroup(groupId).getGroupName();
+ _log.debug("Displaying rejected membership requests for the group: " + groupName);
+ try{
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MMM-yyyy", httpServletRequest.getLocale());
+
+ reqs = lm.listMembershipRequestsByGroup(groupId);
+ if(reqs.size() > 0){
+ for(GCubeMembershipRequest gcmr : reqs){
+ if(gcmr.getStatus() == MembershipRequestStatus.DENIED){
+ JSONObject jo = JSONFactoryUtil.createJSONObject();
+ jo.put("requestDate", dateFormat.format(gcmr.getCreateDate()));
+ jo.put("requestDateObject", gcmr.getCreateDate());
+ jo.put("rejectionDate", dateFormat.format(gcmr.getReplyDate()));
+ jo.put("rejectionDateObject", gcmr.getReplyDate());
+ jo.put("validationDate", dateFormat.format(gcmr.getReplyDate()));
+ jo.put("requestComments", gcmr.getComment());
+ jo.put("requestId", gcmr.getMembershipRequestId());
+ GCubeUser gcu = gcmr.getRequestingUser();
+ jo.put("userName", gcu.getUsername());
+ jo.put("userFullName", gcu.getFullname());
+ jo.put("userEmail", gcu.getEmail());
+ jo.put("userId", gcu.getUserId());
+ LiferayRoleManager lrm = new LiferayRoleManager();
+ List rolesList = lrm.listRolesByUserAndGroup(gcu.getUserId(), groupId);
+ List roles = new ArrayList();
+ for(GCubeRole gcr : rolesList){
+ roles.add(gcr.getRoleName());
+ }
+ jo.put("userSiteRoles", roles.toString());
+ ja.put(jo);
+ }
+ }
+ }
+ _log.debug("The admin: " + currentUser.getUsername() + " displayed rejectedMembershipRequests for the site: " + groupName);
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+ return ja;
+ }
+
+ protected JSONArray usersForCurrrentUsersTablePlusRoles( Long groupId,
+ long[] usersIDs, String[] theRoles, String[] usersTeams,
+ boolean deletePreviousRoles, long selfId, int typeOfChangesUpponUserMode,
+ ResourceRequest request
+ ) throws SystemException, PortalException, UserManagementSystemException, UserRetrievalFault, RoleRetrievalFault, GroupRetrievalFault{
+ LiferayGroupManager lgm = new LiferayGroupManager();
+ String groupName = lgm.getGroup(groupId).getGroupName();
+
+ LiferayRoleManager lrm = new LiferayRoleManager();
+ LiferayUserManager lum = new LiferayUserManager();
+
+ HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest(request);
+
+ _log.debug("Editing users for the group: " + groupName);
+
+ if(usersIDs.length == 1){//You can delete previous roles of a single user only.
+ GCubeUser gcu = lum.getUserById(usersIDs[0]);
+ long[] roleIDs = new long[theRoles.length];
+ GCubeRole[] gCubeRoles = new GCubeRole[theRoles.length];
+ long[] newRoleIDs = new long[theRoles.length];
+ List oldRoles = lrm.listRolesByUserAndGroup(usersIDs[0], groupId);
+
+ for(int i=0;i teams = lrm.listTeamsByGroup(groupId);
+// if(teams.size() > 0 ){
+// lrm.deleteUserTeams(gcu.getUserId(), teams);
+// }
+ } else if(deletePreviousRoles && typeOfChangesUpponUserMode == ASSIGN_ROLES_TO_USERS){
+ _log.debug("ASSIGN_ROLES_TO_USERS");
+ try{
+ lrm.removeAllRolesFromUser(gcu.getUserId(), groupId);
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+ } else if(deletePreviousRoles && typeOfChangesUpponUserMode == ASSIGN_TEAMS_TO_USERS){
+ _log.debug("ASSIGN_TEAMS_TO_USERS");
+ }
+
+ long[] gCubeTeamIDs = new long[usersTeams.length];
+ String[] gCubeTeamNames = new String[usersTeams.length];
+ StringBuffer teamNames = new StringBuffer();
+
+ for(int i=0; i oldUserTeams = lrm.listTeamsByUserAndGroup(gcu.getUserId(), groupId);
+ List oldUserTeamsIDs = new ArrayList();
+ for(Iterator it = oldUserTeams.iterator(); it.hasNext();) {
+ GCubeTeam gct = it.next();
+ oldUserTeamsIDs.add(gct.getTeamId());
+ }
+ List newUserTeams = new ArrayList();
+ List newUserTeamsIDs = new ArrayList();
+
+ for(long gCubeTeamID : gCubeTeamIDs) {
+ GCubeTeam gct = lrm.getTeam(gCubeTeamID);
+ newUserTeams.add(gct);
+ newUserTeamsIDs.add(gCubeTeamID);
+ }
+
+ List newTeamsToBeAssociatedWith = new ArrayList(newUserTeams);
+ List newTeamsIDsToBeAssociatedWith = new ArrayList(newUserTeamsIDs);
+
+ newUserTeams.removeAll(oldUserTeams);
+ newUserTeamsIDs.removeAll(oldUserTeamsIDs);
+
+ for(Long gctID : newUserTeamsIDs){
+ lrm.assignTeamToUser(gcu.getUserId(), gctID);
+ GCubeTeam gct = lrm.getTeam(gctID);
+ notifyUsersByEmailOnTeamAssignment(groupId, gcu, lum.getUserById(selfId), lgm.getGroup(groupId), httpServletRequest, gct);
+ }
+
+ if(newUserTeamsIDs.size() > 0)
+ _log.debug("User: " + gcu.getUsername() + " was added to following teams: " + teamNames);
+ else
+ _log.debug("User: " + gcu.getUsername() + " was added to no new teams");
+
+ oldUserTeams.removeAll(newTeamsToBeAssociatedWith);
+ oldUserTeamsIDs.removeAll(newTeamsIDsToBeAssociatedWith);
+ List teamsIDsToBeDisassociatedFrom = new ArrayList(oldUserTeamsIDs);
+ if(teamsIDsToBeDisassociatedFrom.size() != 0) {
+ List teamsToBeDisassociatedFrom = new ArrayList();
+ for(Iterator it = teamsIDsToBeDisassociatedFrom.iterator(); it.hasNext();){
+ teamsToBeDisassociatedFrom.add(lrm.getTeam(it.next()));
+ }
+
+ lrm.deleteUserTeams(gcu.getUserId(), teamsToBeDisassociatedFrom);
+
+ for(int i = 0; i < teamsToBeDisassociatedFrom.size(); i++) {
+
+ notifyUsersByEmailOnTeamDismissal(groupId, gcu, lum.getUserById(selfId), lgm.getGroup(groupId), httpServletRequest, teamsToBeDisassociatedFrom.get(i));
+ }
+ }
+ }
+ } catch (TeamRetrievalFault e) {
+ _log.debug("User: " + gcu.getUsername() + " failed to be added to the following teams: " + teamNames);
+ e.printStackTrace();
+ }
+
+ boolean rolesAssignmentSucceeded = lrm.assignRolesToUser(gcu.getUserId(), groupId, roleIDs);
+
+ if(rolesAssignmentSucceeded) {
+ List rolesComingFromTheUI = gCubeRoles.length > 0 ? Arrays.asList(gCubeRoles) : new ArrayList();
+ _log.info("rolesComingFromTheUI");
+ rolesComingFromTheUI.forEach(role -> _log.info("Role UI: " + role.getRoleName()));
+ List roleIdsComingFromTheUICopy = new ArrayList();
+ List roleIdsComingFromTheUI = new ArrayList();
+ for(long id : newRoleIDs) {
+ roleIdsComingFromTheUI.add(id);
+ roleIdsComingFromTheUICopy.add(id);
+ }
+
+ List oldRoleIds = oldRoles.stream().map(role -> new Long(role.getRoleId())).collect(Collectors.toList());
+
+ //new roles
+ roleIdsComingFromTheUI.removeAll(oldRoleIds);
+ List newRoles = new ArrayList(rolesComingFromTheUI.stream().filter(role -> roleIdsComingFromTheUI.contains(role.getRoleId())).collect(Collectors.toList()) );
+ _log.info("New roles");
+ newRoles.forEach(role -> _log.info("NewRole: " + role.getRoleName()));
+
+ //revoked roles
+ oldRoleIds.removeAll(roleIdsComingFromTheUICopy);
+ List rolesRevoked = new ArrayList( oldRoles.stream().filter(role -> oldRoleIds.contains(role.getRoleId())).collect(Collectors.toList()) );
+ _log.info("Revoked roles");
+ rolesRevoked.forEach(role -> _log.info("RevokedRole: " + role.getRoleName()));
+
+ if(newRoles.size() > 0 || rolesRevoked.size() > 0)
+ notifyUsersByEmailOnRoleAssignemntRevoke(groupId, gcu,lum.getUserById(selfId), lgm.getGroup(groupId), newRoles, rolesRevoked, httpServletRequest);
+ }
+
+ _log.debug("User: " + currentUser.getUsername() + " is editing the roles of user: "+ gcu.getUsername() + " for the site: " + groupName + "and deletes all previous site-roles");
+ }else{
+
+ List existingGCubeTeams = lrm.listTeamsByGroup(groupId);
+
+ for(long uid : usersIDs){
+ GCubeUser user = lum.getUserById(uid);
+ GCubeUser gcu = lum.getUserByUsername(user.getUsername());
+
+ List usersInTeam = new ArrayList();
+
+ Set existingGCubeTeamsIDs = new HashSet();
+
+ for(GCubeTeam gCubeTeam : existingGCubeTeams){
+ try {
+ usersInTeam = lum.listUsersByTeam(gCubeTeam.getTeamId());
+
+ if(usersInTeam.contains(user)){
+ existingGCubeTeamsIDs.add(gCubeTeam.getTeamId());
+ }
+ } catch (TeamRetrievalFault e) {
+ _log.debug("Failed to retrieve team with teamId: " + gCubeTeam.getTeamId());
+ e.printStackTrace();
+ }
+ }
+
+ long[] roleIDs = new long[theRoles.length];
+
+ GCubeRole[] gCubeRoles = new GCubeRole[theRoles.length];
+ long[] newRoleIDs = new long[theRoles.length];
+ for(int i=0;i oldRoles = lrm.listRolesByUserAndGroup(usersIDs[0], groupId);
+
+ @SuppressWarnings("unused")
+ boolean rolesAssignmentSucceeded = lrm.assignRolesToUser(gcu.getUserId(), groupId, roleIDs);
+
+ if(rolesAssignmentSucceeded) {
+ List rolesComingFromTheUI = gCubeRoles.length > 0 ? Arrays.asList(gCubeRoles) : new ArrayList();
+ List roleIdsComingFromTheUICopy = new ArrayList();
+ List roleIdsComingFromTheUI = new ArrayList();
+
+ for(long id : newRoleIDs) {
+ roleIdsComingFromTheUI.add(id);
+ roleIdsComingFromTheUICopy.add(id);
+ }
+
+ List oldRoleIds = oldRoles.stream().map(role -> new Long(role.getRoleId())).collect(Collectors.toList());
+
+ //new roles
+ roleIdsComingFromTheUI.removeAll(oldRoleIds);
+ List newRoles = new ArrayList(rolesComingFromTheUI.stream().filter(role -> roleIdsComingFromTheUI.contains(role.getRoleId())).collect(Collectors.toList()) );
+
+ //revoked roles
+ oldRoleIds.removeAll(roleIdsComingFromTheUICopy);
+ List rolesRevoked = new ArrayList( oldRoles.stream().filter(role -> oldRoleIds.contains(role.getRoleId())).collect(Collectors.toList()) );
+
+
+ if(newRoles.size() > 0 && rolesRevoked.size() > 0)
+ notifyUsersByEmailOnRoleAssignemntRevoke(groupId, gcu,lum.getUserById(selfId), lgm.getGroup(groupId), newRoles, rolesRevoked, httpServletRequest);
+ }
+
+ List gCubeTeamIDs = new ArrayList();
+ String[] gCubeTeamNames = new String[usersTeams.length];
+ StringBuffer teamNames = new StringBuffer();
+
+ for(int i=0; i oldUserTeams = lrm.listTeamsByUserAndGroup(gcu.getUserId(), groupId);
+ List oldUserTeamsIDs = new ArrayList();
+ for(Iterator it = oldUserTeams.iterator(); it.hasNext();) {
+ GCubeTeam gct = it.next();
+ oldUserTeamsIDs.add(gct.getTeamId());
+ }
+ List newUserTeams = new ArrayList();
+ List newUserTeamsIDs = new ArrayList();
+
+ for(long gCubeTeamID : gCubeTeamIDs) {
+ GCubeTeam gct = lrm.getTeam(gCubeTeamID);
+ newUserTeams.add(gct);
+ newUserTeamsIDs.add(gCubeTeamID);
+ }
+
+ List newTeamsToBeAssociatedWith = new ArrayList(newUserTeams);
+ List newTeamsIDsToBeAssociatedWith = new ArrayList(newUserTeamsIDs);
+ newUserTeams.removeAll(oldUserTeams);
+ newUserTeamsIDs.removeAll(oldUserTeamsIDs);
+
+ for(Long gctID : newUserTeamsIDs){
+ lrm.assignTeamToUser(gcu.getUserId(), gctID);
+ GCubeTeam gct = lrm.getTeam(gctID);
+ notifyUsersByEmailOnTeamAssignment(groupId, gcu, lum.getUserById(selfId), lgm.getGroup(groupId), httpServletRequest, gct);
+ }
+
+ if(newUserTeamsIDs.size() > 0)
+ _log.debug("User: " + gcu.getUsername() + " was added to following teams: " + teamNames);
+ else
+ _log.debug("User: " + gcu.getUsername() + " was added to no new teams");
+
+ oldUserTeams.removeAll(newTeamsToBeAssociatedWith);
+ oldUserTeamsIDs.removeAll(newTeamsIDsToBeAssociatedWith);
+ List teamsIDsToBeDisassociatedFrom = new ArrayList(oldUserTeamsIDs);
+ if(teamsIDsToBeDisassociatedFrom.size() != 0) {
+ List teamsToBeDisassociatedFrom = new ArrayList();
+ for(Iterator it = teamsIDsToBeDisassociatedFrom.iterator(); it.hasNext();){
+ teamsToBeDisassociatedFrom.add( lrm.getTeam(it.next()) );
+ }
+
+// lrm.deleteUserTeams(gcu.getUserId(), teamsToBeDisassociatedFrom);
+
+// for(int index = 0; index < teamsToBeDisassociatedFrom.size(); index++) {
+// notifyUsersByEmailOnTeamDismissal(groupId, gcu, lum.getUserById(selfId), lgm.getGroup(groupId), httpServletRequest, teamsToBeDisassociatedFrom.get(index));
+// }
+ }
+ } catch (TeamRetrievalFault e) {
+ _log.debug("User: " + gcu.getUsername() + " failed to be added to the following teams: " + teamNames);
+ e.printStackTrace();
+ }
+
+ _log.debug("User: " + currentUser.getUsername() + " is editing the roles of user: "+ user.getUsername() + " for the site: " + groupName);
+ }
+ }
+
+ return currentGroupUsers( groupId, selfId, httpServletRequest);
+ }
+
+ protected void removeUsersFromGroup(PortletRequest portletRequest, ResourceRequest request, long groupId, long[] userIDs,
+ long[] membershipRequestsIDs, boolean sendDismissalEmail)
+ throws SystemException, PortalException, UserManagementSystemException,
+ UserRetrievalFault, GroupRetrievalFault{
+
+ UserManager um = new LiferayUserManager();
+ GroupManager gm = new LiferayGroupManager();
+ RoleManager rm = new LiferayRoleManager();
+
+ String groupName = gm.getGroup(groupId).getGroupName();
+ String groupNameForSubject = "";
+
+ //Email
+ if(gm.isRootVO(groupId)) {
+ groupNameForSubject += " Virtual Organization";
+ groupName = "" + groupName + " Virtual Organization";
+ } else if(gm.isVRE(groupId)) {
+ groupNameForSubject += " Virtual Research Environment";
+ groupName = "" + groupName + " Virtual Research Environment";
+ }else{
+ groupNameForSubject = groupName;
+ _log.debug("isRootVO: " + gm.isRootVO(groupId) + "\nisVRE: " + gm.isVRE(groupId));
+ }
+ HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest(request);
+
+
+ String emailSubject = EmailPartsConstruction.subjectForUserDismissalFromSite(
+ "userDismissalFromSiteSubject", groupNameForSubject);
+
+ List recipients = new ArrayList();
+
+ _log.debug("Dissmissing users from group: " + groupName);
+ ArrayList managersEmails = getVREManagersEmailsForGroup(groupId);
+ for(int i = 0; i < userIDs.length; i++){
+
+ GCubeUser gcu = um.getUserById(userIDs[i]);
+ um.dismissUserFromGroup(groupId, gcu.getUserId());
+
+ if(sendDismissalEmail){
+ String emailRecipient = gcu.getEmail();
+ recipients.add(emailRecipient);
+ }
+
+ try {
+ try{
+ rm.removeAllRolesFromUser(gcu.getUserId(), groupId);
+ _log.debug("Removing all roles from user succeeded");
+ }catch(Exception e){
+ _log.debug("Removing all roles from user failed");
+ e.printStackTrace();
+ }
+
+ try {
+ LiferayRoleManager lrm = new LiferayRoleManager();
+ List teams = lrm.listTeamsByGroup(groupId);
+ if(teams.size() > 0 ){
+ lrm.deleteUserTeams(gcu.getUserId(), teams);
+ _log.debug("Removing all groups from user succeeded");
+ }
+ }catch(Exception e){
+ _log.debug("Removing all groups from user failed");
+ e.printStackTrace();
+ }
+
+ GCubeMembershipRequest mr = um.getMembershipRequestsById(membershipRequestsIDs[i]);
+ if (mr != null) {
+ um.rejectMembershipRequest(mr.getRequestingUser().getUserId(), groupId, mr.getReplierUser().getUsername(), mr.getManagerReplyComment());
+ }
+
+ _log.debug("User: " + currentUser.getUsername() + " dismissing the user: "+ gcu.getUsername() + " from the site: " + groupName);
+ } catch (UserManagementPortalException e) {
+ _log.debug("Membership Request rejection failed");
+ e.printStackTrace();
+ }
+
+ if(sendDismissalEmail) {
+ int recSize = managersEmails.size() + 1;
+ Recipient[] recs = new Recipient[recSize];
+ recs[0] = new Recipient(new EmailAddress(gcu.getEmail()), RecipientType.TO);
+ if (!managersEmails.isEmpty()) {
+ int j = 1;
+ for (String mEmail : managersEmails){
+ recs[j] = new Recipient(new EmailAddress(mEmail), RecipientType.BCC);
+ j++;
+ }
+ }
+
+ TemplateUserHasBeenUnregisteredVRE requestAcceptedTemplate = new TemplateUserHasBeenUnregisteredVRE(
+ gcu, um.getUserById(PortalUtil.getUserId(httpServletRequest)), gm.getGroup(groupId),
+ PortalContext.getConfiguration().getGatewayName(httpServletRequest), PortalContext.getConfiguration().getGatewayURL(httpServletRequest));
+ EmailTemplateService.send(emailSubject, (org.gcube.common.portal.mailing.templates.Template)requestAcceptedTemplate, httpServletRequest, recs);
+ }
+ }
+ }
+
+ protected void currentUsersTableSection(
+ boolean currentUsersTable,
+ boolean deleteUsersFromCurrentUsersTable,
+ int modeCurrentUsersTable,
+ long groupId,
+ JSONObject jsonObject,
+
+ PortletRequest portletRequest,
+ ResourceRequest request, long selfId
+ ){
+ HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest(request);
+
+ if(currentUsersTable && !deleteUsersFromCurrentUsersTable){
+ if(modeCurrentUsersTable == REFRESH_CURRENT_USERS_TABLE){
+ try {
+ jsonObject.put("currentUsers", currentGroupUsers( groupId, selfId, httpServletRequest));
+ } catch (GroupRetrievalFault e) {
+ e.printStackTrace();
+ } catch (UserManagementSystemException e) {
+ e.printStackTrace();
+ } catch (UserRetrievalFault e) {
+ e.printStackTrace();
+ }
+ } else if(modeCurrentUsersTable == EDIT_CURRENT_USERS_TABLE){//edit
+ try {
+ long[] selectedUsers = ParamUtil.getLongValues(portletRequest, "selectedUsers[]");
+ String[] usersRoles = ParamUtil.getParameterValues(portletRequest, "usersRoles[]");
+ String[] usersTeams = ParamUtil.getParameterValues(portletRequest, "usersTeams[]");
+ boolean deletePreviousRoles = ParamUtil.getBoolean(portletRequest, "deletePreviousRoles");
+ int typeOfChangesUpponUserMode = ParamUtil.getInteger(portletRequest, "typeOfChangesUpponUserMode");
+ jsonObject.put("currentUsers", usersForCurrrentUsersTablePlusRoles( groupId, selectedUsers, usersRoles,
+ usersTeams, deletePreviousRoles, selfId,
+ typeOfChangesUpponUserMode, request));
+ } catch (SystemException e) {
+ e.printStackTrace();
+ } catch (PortalException e) {
+ e.printStackTrace();
+ } catch (UserManagementSystemException e) {
+ e.printStackTrace();
+ } catch (UserRetrievalFault e) {
+ e.printStackTrace();
+ } catch (RoleRetrievalFault e) {
+ e.printStackTrace();
+ } catch (GroupRetrievalFault e) {
+ e.printStackTrace();
+ }
+ }
+
+ }else if(currentUsersTable && deleteUsersFromCurrentUsersTable){//delete
+
+ try {
+ long[] selectedUsers = ParamUtil.getLongValues(portletRequest, "selectedUsers[]");
+ long[] membershipRequestsIDs = ParamUtil.getLongValues(request, "membershipRequestsIDs[]");
+ boolean sendDismissalEmail = ParamUtil.getBoolean(portletRequest, "sendDismissalEmail");
+ removeUsersFromGroup(portletRequest, request, groupId, selectedUsers, membershipRequestsIDs, sendDismissalEmail);
+ jsonObject.put("currentUsers", currentGroupUsers( groupId, selfId, httpServletRequest));
+ } catch (SystemException e) {
+ e.printStackTrace();
+ } catch (PortalException e) {
+ e.printStackTrace();
+ } catch (UserManagementSystemException e) {
+ e.printStackTrace();
+ } catch (UserRetrievalFault e) {
+ e.printStackTrace();
+ } catch (GroupRetrievalFault e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ protected void membershipRequestsSection(
+ boolean fetchUsersRequests, int modeMembershipRequestsTable,
+ JSONObject jsonObject, ResourceRequest request,
+ PortletRequest portletRequest, long groupId,
+ HttpServletRequest httpServletRequest){
+ if(fetchUsersRequests){
+ if(modeMembershipRequestsTable == REFRESH_MEMBERSHIP_REQUESTS_TABLE){//Refresh
+ try {
+ jsonObject.put("currentUsersRequests", currentGroupUsersRequests(groupId, httpServletRequest));
+ } catch (GroupRetrievalFault e) {
+ e.printStackTrace();
+ } catch (UserManagementSystemException e) {
+ e.printStackTrace();
+ } catch (UserRetrievalFault e) {
+ e.printStackTrace();
+ }
+ }else if(modeMembershipRequestsTable == APPROVE_MEMBERSHIP_REQUESTS_TABLE){//Accept
+ try {
+ long[] reqIDs = ParamUtil.getLongValues(portletRequest, "membershipRequestsIds[]");
+ Long managerId = ParamUtil.getLong(request, "managerId");
+ acceptMemebershipRequestAndAddUsersToGroup(portletRequest, request, reqIDs, groupId, managerId);
+ jsonObject.put("currentUsersRequests", currentGroupUsersRequests(groupId, httpServletRequest));
+ }catch (SystemException e) {
+ e.printStackTrace();
+ }catch (GroupRetrievalFault e) {
+ e.printStackTrace();
+ } catch (UserManagementSystemException e) {
+ e.printStackTrace();
+ } catch (UserRetrievalFault e) {
+ e.printStackTrace();
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ } catch (PortalException e) {
+ e.printStackTrace();
+ }
+ }else if(modeMembershipRequestsTable == REJECT_MEMBERSHIP_REQUESTS_TABLE){//Reject
+ try {
+ long[] reqIDs = ParamUtil.getLongValues(portletRequest, "membershipRequestsIds[]");
+ Long managerId = ParamUtil.getLong(request, "managerId");
+ boolean CustomRejectionEmailFromAdmin = ParamUtil.getBoolean(request, "CustomRejectionEmailFromAdmin");
+ String CustomRejectionEmailBodyFromAdmin = ParamUtil.getString(request, "CustomRejectionEmailBodyFromAdmin");
+ _log.debug(CustomRejectionEmailFromAdmin + " " + CustomRejectionEmailBodyFromAdmin);
+ rejectMembershipRequests(portletRequest, request, reqIDs, groupId, managerId, CustomRejectionEmailFromAdmin, CustomRejectionEmailBodyFromAdmin);
+ jsonObject.put("currentUsersRequests", currentGroupUsersRequests(groupId, httpServletRequest));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ } catch (SystemException e) {
+ e.printStackTrace();
+ } catch (PortalException e) {
+ e.printStackTrace();
+ } catch (GroupRetrievalFault e) {
+ e.printStackTrace();
+ } catch (UserManagementSystemException e) {
+ e.printStackTrace();
+ } catch (UserRetrievalFault e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ protected void rejectedMembershipRequestsSection(
+ boolean fetchUsersRejectedRequests,
+ JSONObject jsonObject,
+ ResourceRequest request, PortletRequest portletRequest,
+ long groupId, HttpServletRequest httpServletRequest) {
+
+ if(fetchUsersRejectedRequests){//Rejected
+ try {
+ jsonObject.put("currentUsersRequests", currentGroupRejectedUsersRequests( groupId, httpServletRequest));
+ }catch (GroupRetrievalFault e) {
+ e.printStackTrace();
+ } catch (UserManagementSystemException e) {
+ e.printStackTrace();
+ } catch (UserRetrievalFault e) {
+ e.printStackTrace();
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+
+ protected void countUsersMembershipRequestsSection(
+ boolean countUsersMembershipRequests,
+ JSONObject jsonObject, long groupId){
+ if(countUsersMembershipRequests){
+ try {
+ jsonObject.put("countUsersMembershipRequests", numberOfRequestsForSpecificGroup(groupId));
+ } catch (PortalException e) {
+ e.printStackTrace();
+ } catch (SystemException e) {
+ e.printStackTrace();
+ } catch (UserManagementSystemException e) {
+ e.printStackTrace();
+ } catch (GroupRetrievalFault e) {
+ e.printStackTrace();
+ } catch (UserRetrievalFault e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ protected void siteTeamsForTheCurrentGroupSection( ResourceRequest request, long managerID, int modeSiteTeams, long groupId, JSONObject jsonObject) throws SystemException{
+ HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest(request);
+ RoleManager rm = new LiferayRoleManager();
+ LiferayGroupManager lgm = new LiferayGroupManager();
+ String groupName = "";
+ try {
+ groupName = lgm.getGroup(groupId).getGroupName();
+ } catch (UserManagementSystemException | GroupRetrievalFault e2) {
+ e2.printStackTrace();
+ }
+
+ if(modeSiteTeams == REFRESH_SITE_TEAMS_TABLE){
+ fetchSiteTeams(groupId, jsonObject, PortalUtil.getHttpServletRequest(request));
+ } else if(modeSiteTeams == EDIT_SITE_TEAMS_TABLE){
+ long siteTeamID = ParamUtil.getLong(request, "siteTeamID");
+ String siteTeamName = ParamUtil.getString(request, "siteTeamName");
+ String siteTeamDescription = ParamUtil.getString(request, "siteTeamDescription");
+ _log.debug("Editing teams for group: " + groupName + " ...");
+
+ try {
+ String teamName = rm.getTeam(siteTeamID).getTeamName();
+ try {
+ rm.updateTeam(siteTeamID, siteTeamName, siteTeamDescription);
+ } catch (TeamRetrievalFault e) {
+ e.printStackTrace();
+ }
+ _log.debug("Edited team: " +teamName + " to Team Name: " + siteTeamName + " and Team Description: " + siteTeamDescription);
+ } catch (UserManagementSystemException e1) {
+ e1.printStackTrace();
+ } catch (TeamRetrievalFault e1) {
+ e1.printStackTrace();
+ }
+ fetchSiteTeams(groupId, jsonObject, httpServletRequest);
+
+ } else if(modeSiteTeams == SITE_TEAMS_TABLE_CREATE_GROUP){
+ String siteTeamName = ParamUtil.getString(request, "siteTeamName");
+ String siteTeamDescription = ParamUtil.getString(request, "siteTeamDescription");
+ long adminUserId = currentUser.getUserId();
+ _log.debug("Create team for group: " + groupName + " ...");
+
+ try {
+ rm.createTeam(adminUserId, groupId, siteTeamName, siteTeamDescription);
+ _log.debug("Added team with Name: " + siteTeamName + " and Team Description: " + siteTeamDescription);
+ } catch (GroupRetrievalFault | TeamRetrievalFault | UserManagementSystemException e) {
+ e.printStackTrace();
+ }
+ fetchSiteTeams(groupId, jsonObject, httpServletRequest);
+
+ }
+ else if(modeSiteTeams == DELETE_SITE_TEAMS_TABLE){
+ long siteTeamID = ParamUtil.getLong(request, "siteTeamID");
+ String teamName;
+ try {
+ teamName = rm.getTeam(siteTeamID).getTeamName();
+ rm.deleteTeam(siteTeamID);
+ _log.debug("Deleted team with Name: " + teamName);
+ } catch (UserManagementSystemException | TeamRetrievalFault e) {
+ e.printStackTrace();
+ }
+ fetchSiteTeams(groupId, jsonObject, httpServletRequest);
+ }
+ }
+
+ void fetchSiteTeams(long groupId, JSONObject jsonObject, HttpServletRequest httpServletRequest){
+ GroupManager gm = new LiferayGroupManager();
+ RoleManager rm = new LiferayRoleManager();
+ UserManager um = new LiferayUserManager();
+
+ List currentGroupTeams = new ArrayList();
+ try {
+ currentGroupTeams = rm.listTeamsByGroup(groupId);
+ } catch (GroupRetrievalFault e2) {
+ e2.printStackTrace();
+ }
+ JSONArray ja = JSONFactoryUtil.createJSONArray();
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MMM-yyyy", httpServletRequest.getLocale());
+
+ for(GCubeTeam siteTeam : currentGroupTeams){
+ JSONObject jo = JSONFactoryUtil.createJSONObject();
+ String siteTeamName = siteTeam.getTeamName();
+ siteTeamName = siteTeamName.replace("'", "'");//Escaping single quote char
+ jo.put("Name", siteTeamName);
+ jo.put("Description", siteTeam.getDescription());
+ jo.put("CreationDate", dateFormat.format(siteTeam.getCreatedate()));
+ jo.put("CreationDateObject", siteTeam.getCreatedate());
+ jo.put("LastModificationDate", dateFormat.format(siteTeam.getModifiedDate()));
+ jo.put("LastModificationDateObject", siteTeam.getModifiedDate());
+
+ int numberOfUsersInTeam = 0;
+ List teamUsers = new ArrayList();
+ try {
+ teamUsers = um.listUsersByTeam(siteTeam.getTeamId());
+ numberOfUsersInTeam = teamUsers.size();
+ } catch (UserManagementSystemException | TeamRetrievalFault | UserRetrievalFault e) {
+ e.printStackTrace();
+ }
+ jo.put("NumberOfUsers", numberOfUsersInTeam);
+ jo.put("TeamID", siteTeam.getTeamId());
+
+ JSONArray ja1 = JSONFactoryUtil.createJSONArray();
+
+ List users = new ArrayList();
+
+ try {
+ users = um.listUsersByGroup(groupId);
+ } catch (UserManagementSystemException | GroupRetrievalFault | UserRetrievalFault e1) {
+ e1.printStackTrace();
+ }
+
+ for(GCubeUser u : users) {
+ JSONObject jo1 = JSONFactoryUtil.createJSONObject();
+ try {
+ if(teamUsers.contains(u)){
+ jo1.put("fullName", u.getFullname());
+ jo1.put("screenName", u.getUsername());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ ja1.put(jo1);
+ }
+ jo.put("siteTeamUsers", ja1);
+ try {
+ jo.put("CreatorName", um.getUserById(siteTeam.getUserId()).getFullname());
+ } catch (UserManagementSystemException | UserRetrievalFault e) {
+ jo.put("CreatorName", "");
+ e.printStackTrace();
+ }
+ ja.put(jo);
+ }
+
+ jsonObject.put("siteTeams", ja);
+ String groupname = null;
+ try {
+ groupname = gm.getGroup(groupId).getGroupName();
+ } catch (UserManagementSystemException | GroupRetrievalFault e) {
+ e.printStackTrace();
+ }
+ _log.debug("Retrieving teams for the site: " + groupname);
+ }
+
+ void fetchRolesNames(long groupId, JSONObject jsonObject){
+ LiferayRoleManager lrm = new LiferayRoleManager();
+ List rolesList = lrm.listAllGroupRoles();
+ JSONArray ja = JSONFactoryUtil.createJSONArray();
+ LiferayGroupManager lgm = new LiferayGroupManager();
+ String groupName = "";
+ try {
+ groupName = lgm.getGroup(groupId).getGroupName();
+ } catch (UserManagementSystemException | GroupRetrievalFault e) {
+ e.printStackTrace();
+ }
+ _log.debug("Fetching roles for group: " + groupName + " ...");
+
+ for(GCubeRole role : rolesList){
+ ja.put(role.getRoleName());
+ }
+
+ jsonObject.put("roleNames", ja);
+ }
+
+ void fetchTeamsNames(long groupId, JSONObject jsonObject) throws GroupRetrievalFault{
+ LiferayRoleManager lrm = new LiferayRoleManager();
+ List teamsList = lrm.listTeamsByGroup(groupId);
+ JSONArray ja = JSONFactoryUtil.createJSONArray();
+ LiferayGroupManager lgm = new LiferayGroupManager();
+ String groupName = "";
+ try {
+ groupName = lgm.getGroup(groupId).getGroupName();
+ } catch (UserManagementSystemException e) {
+ e.printStackTrace();
+ }
+ _log.debug("Fetching teams for group: " + groupName + " ...");
+
+ for(GCubeTeam team : teamsList){
+ ja.put(team.getTeamName());
+ }
+
+ jsonObject.put("teamNames", ja);
+ }
+
+ private ArrayList getVREManagersEmailsForGroup(Long groupId) {
+ UserManager um = new LiferayUserManager();
+ ArrayList managersEmails = new ArrayList();
+ Map> usersAndRoles = null;
+ try {
+ usersAndRoles = um.listUsersAndRolesByGroup(groupId);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ Set users = usersAndRoles.keySet();
+ for (GCubeUser usr:users) {
+ List roles = usersAndRoles.get(usr);
+ for (int i = 0; i < roles.size(); i++) {
+ if (roles.get(i).getRoleName().equals("VRE-Manager") || roles.get(i).getRoleName().equals("VO-Admin") ) {
+ managersEmails.add(usr.getEmail());
+ _log.debug("VRE Manager email -> " + usr.getEmail());
+ break;
+ }
+ }
+ }
+ return managersEmails;
+ }
+
+ String getUserRequestRejectionEmailSubject(String gatewayNameForSubject, String groupNameForSubject){
+ String emailSubject = EmailPartsConstruction.subjectForMembershipRequestAcceptanceOrRejection(
+ "membershipRequestRejectionSubject", groupNameForSubject);
+
+ return emailSubject;
+ }
+
+ public void notifyUsersByEmailOnTeamAssignment(
+ long groupId, GCubeUser gcu, GCubeUser manager, GCubeGroup group,
+ HttpServletRequest httpServletRequest, GCubeTeam team) {
+
+
+ _log.debug("notifyUsersByEmailOnTeamAssignment for user: " + gcu.getUsername() + " for team: " + team.getTeamName());
+
+ ArrayList managersEmails = getVREManagersEmailsForGroup(groupId);
+
+ int recSize = managersEmails.size() + 1;
+ Recipient[] recs = new Recipient[recSize];
+ recs[0] = new Recipient(new EmailAddress(gcu.getEmail()), RecipientType.TO);
+ if (!managersEmails.isEmpty()) {
+ int i = 1;
+ for (String mEmail : managersEmails){
+ recs[i] = new Recipient(new EmailAddress(mEmail), RecipientType.BCC);
+ i++;
+ }
+ }
+
+ String properEmailSubject = EmailPartsConstruction.getSiteTeamAssignmentSubject(team.getTeamName());
+
+ TemplateUserHasBeenAssociatedWithGCubeTeam requestTeamAssignmentTemplate = new TemplateUserHasBeenAssociatedWithGCubeTeam(
+ group, team, gcu,
+ manager, new Date(),
+ PortalContext.getConfiguration().getGatewayName(httpServletRequest), PortalContext.getConfiguration().getGatewayURL(httpServletRequest));
+
+ _log.debug("Sending email to user: " + gcu.getUsername() + ". He is being assigned to group: " + team.getTeamName());
+
+ EmailTemplateService.send(properEmailSubject, (org.gcube.common.portal.mailing.templates.Template)requestTeamAssignmentTemplate, httpServletRequest, recs);
+ }
+
+ public void notifyUsersByEmailOnTeamDismissal(
+ long groupId, GCubeUser gcu, GCubeUser manager, GCubeGroup group,
+ HttpServletRequest httpServletRequest, GCubeTeam team) {
+
+ _log.debug("notifyUsersByEmailOnTeamDismissal for user: " + gcu.getUsername() + " for team: " + team.getTeamName());
+
+ ArrayList managersEmails = getVREManagersEmailsForGroup(groupId);
+
+ int recSize = managersEmails.size() + 1;
+ Recipient[] recs = new Recipient[recSize];
+ recs[0] = new Recipient(new EmailAddress(gcu.getEmail()), RecipientType.TO);
+ if (!managersEmails.isEmpty()) {
+ int i = 1;
+ for (String mEmail : managersEmails){
+ recs[i] = new Recipient(new EmailAddress(mEmail), RecipientType.BCC);
+ i++;
+ }
+ }
+
+ String properEmailSubject = EmailPartsConstruction.getSiteTeamDismissalSubject(team.getTeamName());
+
+ TemplateUserHasBeenDissAssociatedWithGCubeTeam requestTeamDismissalTemplate = new TemplateUserHasBeenDissAssociatedWithGCubeTeam(
+ group, team, gcu,
+ manager, new Date(),
+ PortalContext.getConfiguration().getGatewayName(httpServletRequest), PortalContext.getConfiguration().getGatewayURL(httpServletRequest));
+
+ _log.debug("Sending email to user: " + gcu.getUsername() + ". He is being removed from group: " + team.getTeamName());
+
+ EmailTemplateService.send(properEmailSubject, (org.gcube.common.portal.mailing.templates.Template)requestTeamDismissalTemplate, httpServletRequest, recs);
+ }
+
+ public void notifyUsersByEmailOnRoleAssignemntRevoke(long groupId, GCubeUser userToReceiveTheMail, GCubeUser manager, GCubeGroup group, List newRoles, List revokedRoles, HttpServletRequest httpServletRequest) throws UserManagementSystemException, GroupRetrievalFault {
+ _log.debug("Notifying user by email on new role assignme or role revoke fro user: " + userToReceiveTheMail.getUsername() + " for group: " + group.getGroupName() );
+
+ ArrayList managersEmails = getVREManagersEmailsForGroup(groupId);
+
+ int recSize = managersEmails.size() + 1;
+ Recipient[] recs = new Recipient[recSize];
+ recs[0] = new Recipient(new EmailAddress(userToReceiveTheMail.getEmail()), RecipientType.TO);
+ if (!managersEmails.isEmpty()) {
+ int i = 1;
+ for (String mEmail : managersEmails){
+ recs[i] = new Recipient(new EmailAddress(mEmail), RecipientType.BCC);
+ i++;
+ }
+ }
+
+ String properEmailSubject = EmailPartsConstruction.getRoleAssignmentRevokeSubject(new LiferayGroupManager().getGroup(groupId).getGroupName());
+
+ _log.debug("properEmailSubject: " + properEmailSubject);
+
+ TemplateUserRolesModifiedForGroup templateUserRolesModifiedForGroup = new TemplateUserRolesModifiedForGroup(
+ PortalContext.getConfiguration().getGatewayName(httpServletRequest), PortalContext.getConfiguration().getGatewayURL(httpServletRequest),
+ group, userToReceiveTheMail, manager, newRoles, revokedRoles, new Date() );
+
+// _log.debug("--------------------------------------------------------------------------------------------------------------------------------------------");
+// _log.info(templateUserRolesModifiedForGroup.getTextHTML());
+// _log.info("--------------------------------------------------------------------------------------------------------------------------------------------");
+// _log.info(templateUserRolesModifiedForGroup.getTextPLAIN());
+// _log.info("--------------------------------------------------------------------------------------------------------------------------------------------");
+
+ _log.debug("Sending email to user: " + userToReceiveTheMail.getUsername() + ". His roles have been modified within the context of VRE: " + group.getGroupName());
+ _log.info("Sending email to user: " + userToReceiveTheMail.getUsername() + ". His roles have been modified within the context of VRE: " + group.getGroupName());
+
+ EmailTemplateService.send(properEmailSubject, (org.gcube.common.portal.mailing.templates.Template)templateUserRolesModifiedForGroup, httpServletRequest, recs);
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/mail/templates/TemplateUserHasBeenAssociatedWithGCubeTeam.java b/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/mail/templates/TemplateUserHasBeenAssociatedWithGCubeTeam.java
new file mode 100644
index 0000000..6f5cce8
--- /dev/null
+++ b/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/mail/templates/TemplateUserHasBeenAssociatedWithGCubeTeam.java
@@ -0,0 +1,91 @@
+package gr.cite.bluebridge.portlets.admin.usersmanagementportlet.mail.templates;
+
+import java.util.Base64;
+import java.util.Date;
+
+import org.gcube.common.portal.GCubePortalConstants;
+import org.gcube.common.portal.PortalContext;
+import org.gcube.common.portal.mailing.templates.AbstractTemplate;
+import org.gcube.common.portal.mailing.templates.Template;
+import org.gcube.portal.mailing.message.Constants;
+import org.gcube.vomanagement.usermanagement.model.GCubeGroup;
+import org.gcube.vomanagement.usermanagement.model.GCubeMembershipRequest;
+import org.gcube.vomanagement.usermanagement.model.GCubeTeam;
+import org.gcube.vomanagement.usermanagement.model.GCubeUser;
+
+public class TemplateUserHasBeenAssociatedWithGCubeTeam extends AbstractTemplate implements Template {
+ private final String encodedTemplateHTML = "PCEtLSBJbmxpbmVyIEJ1aWxkIFZlcnNpb24gNDM4MGI3NzQxYmI3NTlkNmNiOTk3NTQ1ZjNhZGQyMWFkNDhmMDEwYiAtLT4KPCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBYSFRNTCAxLjAgU3RyaWN0Ly9FTiIgImh0dHA6Ly93d3cudzMub3JnL1RSL3hodG1sMS9EVEQveGh0bWwxLXN0cmljdC5kdGQiPgo8aHRtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJtaW4taGVpZ2h0OiAxMDAlOyBiYWNrZ3JvdW5kLWNvbG9yOiAjZjNmM2YzICFpbXBvcnRhbnQ7Ij4KICA8aGVhZD4KICAgIDxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04IiAvPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCIgLz4KICAgIDx0aXRsZT5UaXRsZTwvdGl0bGU+CiAgPC9oZWFkPgogIDxib2R5IHN0eWxlPSJ3aWR0aDogMTAwJSAhaW1wb3J0YW50OyBtaW4td2lkdGg6IDEwMCU7IC13ZWJraXQtdGV4dC1zaXplLWFkanVzdDogMTAwJTsgLW1zLXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7IC1tb3otYm94LXNpemluZzogYm9yZGVyLWJveDsgLXdlYmtpdC1ib3gtc2l6aW5nOiBib3JkZXItYm94OyBib3gtc2l6aW5nOiBib3JkZXItYm94OyBjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgYmFja2dyb3VuZC1jb2xvcjogI2YzZjNmMyAhaW1wb3J0YW50OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBiZ2NvbG9yPSIjZjNmM2YzICFpbXBvcnRhbnQiPgogIDx0YWJsZSBjbGFzcz0iYm9keSIgZGF0YS1tYWRlLXdpdGgtZm91bmRhdGlvbj0iIiBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IGJhY2tncm91bmQtY29sb3I6ICNmM2YzZjMgIWltcG9ydGFudDsgaGVpZ2h0OiAxMDAlOyB3aWR0aDogMTAwJTsgY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMDsgcGFkZGluZzogMDsiIGJnY29sb3I9IiNmM2YzZjMgIWltcG9ydGFudCI+PHRyIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPjx0ZCBjbGFzcz0iZmxvYXQtY2VudGVyIiBhbGlnbj0iY2VudGVyIiB2YWxpZ249InRvcCIgc3R5bGU9IndvcmQtd3JhcDogYnJlYWstd29yZDsgLXdlYmtpdC1oeXBoZW5zOiBhdXRvOyAtbW96LWh5cGhlbnM6IGF1dG87IGh5cGhlbnM6IGF1dG87IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2UgIWltcG9ydGFudDsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogY2VudGVyOyBmbG9hdDogbm9uZTsgY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMCBhdXRvOyBwYWRkaW5nOiAwOyI+CiAgICAgICAgICA8Y2VudGVyIGRhdGEtcGFyc2VkPSIiIHN0eWxlPSJ3aWR0aDogMTAwJTsgbWluLXdpZHRoOiA1ODBweDsiPgogICAgICAgICAgICA8dGFibGUgYWxpZ249ImNlbnRlciIgY2xhc3M9IndyYXBwZXIgaGVhZGVyIGZsb2F0LWNlbnRlciIgc3R5bGU9IndpZHRoOiAxMDAlOyBib3JkZXItc3BhY2luZzogMDsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogY2VudGVyOyBmbG9hdDogbm9uZTsgbWFyZ2luOiAwIGF1dG87IHBhZGRpbmc6IDA7Ij48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIGNsYXNzPSJ3cmFwcGVyLWlubmVyIiBzdHlsZT0id29yZC13cmFwOiBicmVhay13b3JkOyAtd2Via2l0LWh5cGhlbnM6IGF1dG87IC1tb3otaHlwaGVuczogYXV0bzsgaHlwaGVuczogYXV0bzsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZSAhaW1wb3J0YW50OyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiIHZhbGlnbj0idG9wIj4KICAgICAgICAgICAgICAgICAgPHRhYmxlIGFsaWduPSJjZW50ZXIiIGNsYXNzPSJjb250YWluZXIiIHN0eWxlPSJib3JkZXItc3BhY2luZzogMDsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogaW5oZXJpdDsgd2lkdGg6IDU4MHB4OyBiYWNrZ3JvdW5kOiAjZmVmZWZlOyBtYXJnaW46IDAgYXV0bzsgcGFkZGluZzogMDsiIGJnY29sb3I9IiNmZWZlZmUiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIHN0eWxlPSJ3b3JkLXdyYXA6IGJyZWFrLXdvcmQ7IC13ZWJraXQtaHlwaGVuczogYXV0bzsgLW1vei1oeXBoZW5zOiBhdXRvOyBoeXBoZW5zOiBhdXRvOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlICFpbXBvcnRhbnQ7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0icm93IGNvbGxhcHNlIiBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwb3NpdGlvbjogcmVsYXRpdmU7IGRpc3BsYXk6IHRhYmxlOyBwYWRkaW5nOiAwOyI+PHRib2R5Pjx0ciBzdHlsZT0idmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij48dGggY2xhc3M9InNtYWxsLTYgbGFyZ2UtNiBjb2x1bW5zIGZpcnN0IiBzdHlsZT0id2lkdGg6IDI5OHB4OyBjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwIGF1dG87IHBhZGRpbmc6IDAgMCAxNnB4OyIgYWxpZ249ImxlZnQiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIHN0eWxlPSJib3JkZXItc3BhY2luZzogMDsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgd2lkdGg6IDEwMCU7IHBhZGRpbmc6IDA7Ij48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRoIHN0eWxlPSJjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPiA8aW1nIHNyYz0ie3tHQVRFV0FZX0xPR086VVJMfX0iIHN0eWxlPSJ3aWR0aDogMjAwcHg7IG91dGxpbmU6IG5vbmU7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsgLW1zLWludGVycG9sYXRpb24tbW9kZTogYmljdWJpYzsgbWF4LXdpZHRoOiAxMDAlOyBjbGVhcjogYm90aDsgZGlzcGxheTogYmxvY2s7IiBhbHQ9Int7R0FURVdBWV9OQU1FfX0iIHRpdGxlPSJ7e0dBVEVXQVlfTkFNRX19IiAvPjwvdGg+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+PC90YWJsZT48L3RoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0aCBjbGFzcz0ic21hbGwtNiBsYXJnZS02IGNvbHVtbnMgbGFzdCIgc3R5bGU9IndpZHRoOiAyOThweDsgY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMCBhdXRvOyBwYWRkaW5nOiAwIDAgMTZweDsiIGFsaWduPSJsZWZ0Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwYWRkaW5nOiAwOyI+PHRyIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPjx0aCBzdHlsZT0iY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwIGNsYXNzPSJ0ZXh0LXJpZ2h0IiBzdHlsZT0idGV4dC1hbGlnbjogcmlnaHQ7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDAgMCAxMHB4OyBwYWRkaW5nOiAwOyIgYWxpZ249InJpZ2h0Ij48L3A+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90aD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj48L3RhYmxlPjwvdGg+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+PC90Ym9keT48L3RhYmxlPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT48L3RkPgogICAgICAgICAgICAgIDwvdHI+PC90YWJsZT48dGFibGUgYWxpZ249ImNlbnRlciIgY2xhc3M9ImNvbnRhaW5lciBib2R5LWJvcmRlciBmbG9hdC1jZW50ZXIiIHN0eWxlPSJib3JkZXItc3BhY2luZzogMDsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogY2VudGVyOyB3aWR0aDogNTgwcHg7IGZsb2F0OiBub25lOyBib3JkZXItdG9wLXdpZHRoOiA4cHg7IGJvcmRlci10b3AtY29sb3I6ICMyMjVmOTc7IGJvcmRlci10b3Atc3R5bGU6IHNvbGlkOyBiYWNrZ3JvdW5kOiAjZmVmZWZlOyBtYXJnaW46IDAgYXV0bzsgcGFkZGluZzogMDsiIGJnY29sb3I9IiNmZWZlZmUiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIHN0eWxlPSJ3b3JkLXdyYXA6IGJyZWFrLXdvcmQ7IC13ZWJraXQtaHlwaGVuczogYXV0bzsgLW1vei1oeXBoZW5zOiBhdXRvOyBoeXBoZW5zOiBhdXRvOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlICFpbXBvcnRhbnQ7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0icm93IiBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwb3NpdGlvbjogcmVsYXRpdmU7IGRpc3BsYXk6IHRhYmxlOyBwYWRkaW5nOiAwOyI+PHRib2R5Pjx0ciBzdHlsZT0idmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij48dGggY2xhc3M9InNtYWxsLTEyIGxhcmdlLTEyIGNvbHVtbnMgZmlyc3QgbGFzdCIgc3R5bGU9IndpZHRoOiA1NjRweDsgY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMCBhdXRvOyBwYWRkaW5nOiAwIDE2cHggMTZweDsiIGFsaWduPSJsZWZ0Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwYWRkaW5nOiAwOyI+PHRyIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPjx0aCBzdHlsZT0iY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0ic3BhY2VyIiBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwYWRkaW5nOiAwOyI+PHRib2R5Pjx0ciBzdHlsZT0idmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij48dGQgaGVpZ2h0PSIxNnB4IiBzdHlsZT0iZm9udC1zaXplOiAxNnB4OyBsaW5lLWhlaWdodDogMTZweDsgd29yZC13cmFwOiBicmVhay13b3JkOyAtd2Via2l0LWh5cGhlbnM6IGF1dG87IC1tb3otaHlwaGVuczogYXV0bzsgaHlwaGVuczogYXV0bzsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZSAhaW1wb3J0YW50OyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBtc28tbGluZS1oZWlnaHQtcnVsZTogZXhhY3RseTsgY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPsKgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT48aDQgc3R5bGU9ImNvbG9yOiBpbmhlcml0OyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgd29yZC13cmFwOiBub3JtYWw7IGZvbnQtc2l6ZTogMjRweDsgbWFyZ2luOiAwIDAgMTBweDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij5IaSB7e1JFUVVFU1RJTkdfVVNFUl9GSVJTVF9OQU1FfX0sPGJyIC8+PGEgaHJlZj0ie3tVU0VSX1ZSRU1FTUJFUl9QUk9GSUxFX1VSTH19IiBzdHlsZT0iY29sb3I6ICMyMTk5ZTg7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyB0ZXh0LWRlY29yYXRpb246IG5vbmU7IG1hcmdpbjogMDsgcGFkZGluZzogMDsiPnt7VVNFUl9GVUxMTkFNRX19PC9hPiBoYXMganVzdCBtYWRlIHlvdSBhIG1lbWJlciBvZiB0aGUge3tTRUxFQ1RFRF9URUFNX05BTUV9fSBncm91cCBpbiB0aGUge3tTRUxFQ1RFRF9WUkVfTkFNRX19IFZSRS48L2g0PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGJyIC8+PHAgc3R5bGU9ImNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDAgMCAxMHB4OyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPllvdSBjYW4gYWNjZXNzIHRoZSBWUkUgYXQge3tWUkVfVVJMfX0gdXNpbmcgeW91ciBlbWFpbCB7e1JFUVVFU1RJTkdfVVNFUl9FTUFJTH19LjwvcD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0aCBjbGFzcz0iZXhwYW5kZXIiIHN0eWxlPSJ2aXNpYmlsaXR5OiBoaWRkZW47IHdpZHRoOiAwOyBjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPjwvdGg+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+PC90YWJsZT48L3RoPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT4KPC90ZD4KICAgICAgICAgICAgICAgIDwvdHI+PC90Ym9keT48L3RhYmxlPjwvY2VudGVyPgogICAgICAgIDwvdGQ+CiAgICAgIDwvdHI+PC90YWJsZT48L2JvZHk+CjwvaHRtbD4KCg==";
+ private final String encodedTemplateTEXT = "e3tHQVRFV0FZX05BTUV9fQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCkhpIHt7UkVRVUVTVElOR19VU0VSX0ZJUlNUX05BTUV9fSwKCnt7VVNFUl9GVUxMTkFNRX19IGhhcyBqdXN0IG1hZGUgeW91IGEgbWVtYmVyIG9mIHRoZSB7e1NFTEVDVEVEX1RFQU1fTkFNRX19IGdyb3VwIGluIHRoZSB7e1NFTEVDVEVEX1ZSRV9OQU1FfX0gVlJFLgoKWW91IGNhbiBhY2Nlc3MgdGhlIFZSRSBhdCB7e1ZSRV9VUkx9fSB1c2luZyB5b3VyIGVtYWlsIHt7UkVRVUVTVElOR19VU0VSX0VNQUlMfX0KCg";
+
+ private GCubeGroup theRequestedVRE;
+ private GCubeTeam teamToBeAssociatedWith;
+ private GCubeUser theRequestingUser;
+ private GCubeUser theManagerUser;
+ private Date originalRequestDate;
+ private String vreURL;
+
+ /**
+ *
+ * @param theRequestingUser an instance of @see {@link GCubeUser} representing the user who requested access
+ * @param teamToBeAssociatedWith an instance of @see {@link GCubeTeam} representing the team the user will join
+ * @param theManagerUser an instance of @see {@link GCubeUser} representing the manager who approved the request
+ * @param theRequestedVRE instance of @see {@link GCubeGroup} of the current VRE
+ * @param originalRequestDate the request date as in the associated {@link GCubeMembershipRequest}
+ * @param gatewayName gateway name can be obtained with {@link PortalContext#getGatewayName(javax.servlet.http.HttpServletRequest)}
+ * @param gatewayURL gateway URL name can be obtained with {@link PortalContext#getGatewayURL(javax.servlet.http.HttpServletRequest)}
+ */
+
+ public TemplateUserHasBeenAssociatedWithGCubeTeam(
+ GCubeGroup theRequestedVRE, GCubeTeam teamToBeAssociatedWith, GCubeUser theRequestingUser,
+ GCubeUser theManagerUser, Date originalRequestDate,
+ String gatewayName, String gatewayURL) {
+
+ super(gatewayName, gatewayURL);
+
+ this.theRequestingUser = theRequestingUser;
+ this.theManagerUser = theManagerUser;
+ this.theRequestedVRE = theRequestedVRE;
+ this.originalRequestDate = originalRequestDate;
+ this.teamToBeAssociatedWith = teamToBeAssociatedWith;
+ this.vreURL = new StringBuffer(gatewayURL)
+ .append(GCubePortalConstants.PREFIX_GROUP_URL)
+ .append("/").append(theRequestedVRE.getGroupName().toLowerCase()).toString();
+ }
+
+ @Override
+ public String compile(String templateContent) {
+ String userProfileLink = new StringBuffer(vreURL)
+ .append("/").append(getUserProfileLink(theManagerUser.getUsername())).toString();
+
+ return new String(Base64.getDecoder().decode(templateContent))
+ .replace("{{REQUESTING_USER_FIRST_NAME}}", theRequestingUser.getFirstName())
+ .replace("{{GATEWAY_LOGO:URL}}", getGatewayLogoURL())
+ .replace("{{GATEWAY_NAME}}", getGatewayName())
+ .replace("{{USER_FULLNAME}}", theManagerUser.getFullname())
+ .replace("{{SELECTED_VRE_NAME}}", theRequestedVRE.getGroupName())
+ .replace("{{VRE_URL}}", vreURL)
+ .replace("{{REQUESTING_USER_EMAIL}}", theRequestingUser.getEmail())
+ .replace("{{MANAGE_REQUEST_DATE}}", originalRequestDate.toString())
+ .replace("{{USER_VREMEMBER_PROFILE_URL}}", userProfileLink)
+ .replace("{{SELECTED_TEAM_NAME}}", teamToBeAssociatedWith.getTeamName())
+ ;
+ }
+
+ @Override
+ public String getTextHTML() {
+ return compile(encodedTemplateHTML);
+ }
+
+ @Override
+ public String getTextPLAIN() {
+ return compile(encodedTemplateTEXT);
+ }
+
+ private String getUserProfileLink(String username) {
+ return "profile?"+ new String(
+ Base64.getEncoder().encodeToString(Constants.USER_PROFILE_OID.getBytes())+
+ "="+
+ new String( Base64.getEncoder().encodeToString(username.getBytes()) )
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/mail/templates/TemplateUserHasBeenDissAssociatedWithGCubeTeam.java b/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/mail/templates/TemplateUserHasBeenDissAssociatedWithGCubeTeam.java
new file mode 100644
index 0000000..aadd165
--- /dev/null
+++ b/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/mail/templates/TemplateUserHasBeenDissAssociatedWithGCubeTeam.java
@@ -0,0 +1,90 @@
+package gr.cite.bluebridge.portlets.admin.usersmanagementportlet.mail.templates;
+
+import java.util.Base64;
+import java.util.Date;
+
+import org.gcube.common.portal.GCubePortalConstants;
+import org.gcube.common.portal.PortalContext;
+import org.gcube.common.portal.mailing.templates.AbstractTemplate;
+import org.gcube.common.portal.mailing.templates.Template;
+import org.gcube.portal.mailing.message.Constants;
+import org.gcube.vomanagement.usermanagement.model.GCubeGroup;
+import org.gcube.vomanagement.usermanagement.model.GCubeMembershipRequest;
+import org.gcube.vomanagement.usermanagement.model.GCubeTeam;
+import org.gcube.vomanagement.usermanagement.model.GCubeUser;
+
+public class TemplateUserHasBeenDissAssociatedWithGCubeTeam extends AbstractTemplate implements Template {
+ private final static String encodedTemplateHTML = "PCEtLSBJbmxpbmVyIEJ1aWxkIFZlcnNpb24gNDM4MGI3NzQxYmI3NTlkNmNiOTk3NTQ1ZjNhZGQyMWFkNDhmMDEwYiAtLT4KPCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBYSFRNTCAxLjAgU3RyaWN0Ly9FTiIgImh0dHA6Ly93d3cudzMub3JnL1RSL3hodG1sMS9EVEQveGh0bWwxLXN0cmljdC5kdGQiPgo8aHRtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJtaW4taGVpZ2h0OiAxMDAlOyBiYWNrZ3JvdW5kLWNvbG9yOiAjZjNmM2YzICFpbXBvcnRhbnQ7Ij4KICA8aGVhZD4KICAgIDxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04IiAvPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCIgLz4KICAgIDx0aXRsZT5UaXRsZTwvdGl0bGU+CiAgPC9oZWFkPgoKPGJvZHkgc3R5bGU9IndpZHRoOiAxMDAlICFpbXBvcnRhbnQ7IG1pbi13aWR0aDogMTAwJTsgLXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OiAxMDAlOyAtbXMtdGV4dC1zaXplLWFkanVzdDogMTAwJTsgLW1vei1ib3gtc2l6aW5nOiBib3JkZXItYm94OyAtd2Via2l0LWJveC1zaXppbmc6IGJvcmRlci1ib3g7IGJveC1zaXppbmc6IGJvcmRlci1ib3g7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBiYWNrZ3JvdW5kLWNvbG9yOiAjZjNmM2YzICFpbXBvcnRhbnQ7IG1hcmdpbjogMDsgcGFkZGluZzogMDsiIGJnY29sb3I9IiNmM2YzZjMgIWltcG9ydGFudCI+ICAKICA8dGFibGUgY2xhc3M9ImJvZHkiIGRhdGEtbWFkZS13aXRoLWZvdW5kYXRpb249IiIgc3R5bGU9ImJvcmRlci1zcGFjaW5nOiAwOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBiYWNrZ3JvdW5kLWNvbG9yOiAjZjNmM2YzICFpbXBvcnRhbnQ7IGhlaWdodDogMTAwJTsgd2lkdGg6IDEwMCU7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBiZ2NvbG9yPSIjZjNmM2YzICFpbXBvcnRhbnQiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIGNsYXNzPSJmbG9hdC1jZW50ZXIiIGFsaWduPSJjZW50ZXIiIHZhbGlnbj0idG9wIiBzdHlsZT0id29yZC13cmFwOiBicmVhay13b3JkOyAtd2Via2l0LWh5cGhlbnM6IGF1dG87IC1tb3otaHlwaGVuczogYXV0bzsgaHlwaGVuczogYXV0bzsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZSAhaW1wb3J0YW50OyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBjZW50ZXI7IGZsb2F0OiBub25lOyBjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwIGF1dG87IHBhZGRpbmc6IDA7Ij4KICAgICAgICAgIDxjZW50ZXIgZGF0YS1wYXJzZWQ9IiIgc3R5bGU9IndpZHRoOiAxMDAlOyBtaW4td2lkdGg6IDU4MHB4OyI+CiAgICAgICAgICAgIDx0YWJsZSBhbGlnbj0iY2VudGVyIiBjbGFzcz0id3JhcHBlciBoZWFkZXIgZmxvYXQtY2VudGVyIiBzdHlsZT0id2lkdGg6IDEwMCU7IGJvcmRlci1zcGFjaW5nOiAwOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBjZW50ZXI7IGZsb2F0OiBub25lOyBtYXJnaW46IDAgYXV0bzsgcGFkZGluZzogMDsiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIGNsYXNzPSJ3cmFwcGVyLWlubmVyIiBzdHlsZT0id29yZC13cmFwOiBicmVhay13b3JkOyAtd2Via2l0LWh5cGhlbnM6IGF1dG87IC1tb3otaHlwaGVuczogYXV0bzsgaHlwaGVuczogYXV0bzsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZSAhaW1wb3J0YW50OyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiIHZhbGlnbj0idG9wIj4KICAgICAgICAgICAgICAgICAgPHRhYmxlIGFsaWduPSJjZW50ZXIiIGNsYXNzPSJjb250YWluZXIiIHN0eWxlPSJib3JkZXItc3BhY2luZzogMDsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogaW5oZXJpdDsgd2lkdGg6IDU4MHB4OyBiYWNrZ3JvdW5kOiAjZmVmZWZlOyBtYXJnaW46IDAgYXV0bzsgcGFkZGluZzogMDsiIGJnY29sb3I9IiNmZWZlZmUiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIHN0eWxlPSJ3b3JkLXdyYXA6IGJyZWFrLXdvcmQ7IC13ZWJraXQtaHlwaGVuczogYXV0bzsgLW1vei1oeXBoZW5zOiBhdXRvOyBoeXBoZW5zOiBhdXRvOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlICFpbXBvcnRhbnQ7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0icm93IGNvbGxhcHNlIiBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwb3NpdGlvbjogcmVsYXRpdmU7IGRpc3BsYXk6IHRhYmxlOyBwYWRkaW5nOiAwOyI+PHRib2R5Pjx0ciBzdHlsZT0idmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij48dGggY2xhc3M9InNtYWxsLTYgbGFyZ2UtNiBjb2x1bW5zIGZpcnN0IiBzdHlsZT0id2lkdGg6IDI5OHB4OyBjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwIGF1dG87IHBhZGRpbmc6IDAgMCAxNnB4OyIgYWxpZ249ImxlZnQiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIHN0eWxlPSJib3JkZXItc3BhY2luZzogMDsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgd2lkdGg6IDEwMCU7IHBhZGRpbmc6IDA7Ij48dGJvZHk+PHRyIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPjx0aCBzdHlsZT0iY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij4gPGltZyBzcmM9Int7R0FURVdBWV9MT0dPOlVSTH19IiBzdHlsZT0id2lkdGg6IDIwMHB4OyBvdXRsaW5lOiBub25lOyB0ZXh0LWRlY29yYXRpb246IG5vbmU7IC1tcy1pbnRlcnBvbGF0aW9uLW1vZGU6IGJpY3ViaWM7IG1heC13aWR0aDogMTAwJTsgY2xlYXI6IGJvdGg7IGRpc3BsYXk6IGJsb2NrOyIgYWx0PSJ7e0dBVEVXQVlfTkFNRX19IiB0aXRsZT0ie3tHQVRFV0FZX05BTUV9fSI+PC90aD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj48L3Rib2R5PjwvdGFibGU+PC90aD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGggY2xhc3M9InNtYWxsLTYgbGFyZ2UtNiBjb2x1bW5zIGxhc3QiIHN0eWxlPSJ3aWR0aDogMjk4cHg7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDAgYXV0bzsgcGFkZGluZzogMCAwIDE2cHg7IiBhbGlnbj0ibGVmdCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgc3R5bGU9ImJvcmRlci1zcGFjaW5nOiAwOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyB3aWR0aDogMTAwJTsgcGFkZGluZzogMDsiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRoIHN0eWxlPSJjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHAgY2xhc3M9InRleHQtcmlnaHQiIHN0eWxlPSJ0ZXh0LWFsaWduOiByaWdodDsgY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMCAwIDEwcHg7IHBhZGRpbmc6IDA7IiBhbGlnbj0icmlnaHQiPjwvcD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT48L3RoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT48L3RkPgogICAgICAgICAgICAgICAgICAgICAgPC90cj48L3Rib2R5PjwvdGFibGU+PC90ZD4KICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT48dGFibGUgYWxpZ249ImNlbnRlciIgY2xhc3M9ImNvbnRhaW5lciBib2R5LWJvcmRlciBmbG9hdC1jZW50ZXIiIHN0eWxlPSJib3JkZXItc3BhY2luZzogMDsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogY2VudGVyOyB3aWR0aDogNTgwcHg7IGZsb2F0OiBub25lOyBib3JkZXItdG9wLXdpZHRoOiA4cHg7IGJvcmRlci10b3AtY29sb3I6ICMyMjVmOTc7IGJvcmRlci10b3Atc3R5bGU6IHNvbGlkOyBiYWNrZ3JvdW5kOiAjZmVmZWZlOyBtYXJnaW46IDAgYXV0bzsgcGFkZGluZzogMDsiIGJnY29sb3I9IiNmZWZlZmUiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIHN0eWxlPSJ3b3JkLXdyYXA6IGJyZWFrLXdvcmQ7IC13ZWJraXQtaHlwaGVuczogYXV0bzsgLW1vei1oeXBoZW5zOiBhdXRvOyBoeXBoZW5zOiBhdXRvOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlICFpbXBvcnRhbnQ7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0icm93IiBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwb3NpdGlvbjogcmVsYXRpdmU7IGRpc3BsYXk6IHRhYmxlOyBwYWRkaW5nOiAwOyI+PHRib2R5Pjx0ciBzdHlsZT0idmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij48dGggY2xhc3M9InNtYWxsLTEyIGxhcmdlLTEyIGNvbHVtbnMgZmlyc3QgbGFzdCIgc3R5bGU9IndpZHRoOiA1NjRweDsgY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMCBhdXRvOyBwYWRkaW5nOiAwIDE2cHggMTZweDsiIGFsaWduPSJsZWZ0Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwYWRkaW5nOiAwOyI+PHRib2R5Pjx0ciBzdHlsZT0idmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij48dGggc3R5bGU9ImNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgY2xhc3M9InNwYWNlciIgc3R5bGU9ImJvcmRlci1zcGFjaW5nOiAwOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyB3aWR0aDogMTAwJTsgcGFkZGluZzogMDsiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIGhlaWdodD0iMTZweCIgc3R5bGU9ImZvbnQtc2l6ZTogMTZweDsgbGluZS1oZWlnaHQ6IDE2cHg7IHdvcmQtd3JhcDogYnJlYWstd29yZDsgLXdlYmtpdC1oeXBoZW5zOiBhdXRvOyAtbW96LWh5cGhlbnM6IGF1dG87IGh5cGhlbnM6IGF1dG87IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2UgIWltcG9ydGFudDsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgbXNvLWxpbmUtaGVpZ2h0LXJ1bGU6IGV4YWN0bHk7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiIHZhbGlnbj0idG9wIj4mbmJzcDs8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+PC90Ym9keT48L3RhYmxlPjxoNCBzdHlsZT0iY29sb3I6IGluaGVyaXQ7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyB3b3JkLXdyYXA6IG5vcm1hbDsgZm9udC1zaXplOiAyNHB4OyBtYXJnaW46IDAgMCAxMHB4OyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPkhpIHt7UkVRVUVTVElOR19VU0VSX0ZJUlNUX05BTUV9fSw8YnI+PGEgaHJlZj0ie3tVU0VSX1ZSRU1FTUJFUl9QUk9GSUxFX1VSTH19IiBzdHlsZT0iY29sb3I6ICMyMTk5ZTg7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyB0ZXh0LWRlY29yYXRpb246IG5vbmU7IG1hcmdpbjogMDsgcGFkZGluZzogMDsiPnt7VVNFUl9GVUxMTkFNRX19PC9hPiBoYXMganVzdCByZW1vdmVkIHlvdSBmcm9tIHRoZSB7e1NFTEVDVEVEX1RFQU1fTkFNRX19IGdyb3VwIGluIHRoZSB7e1NFTEVDVEVEX1ZSRV9OQU1FfX0gVlJFLjwvaDQ+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGg+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRoIGNsYXNzPSJleHBhbmRlciIgc3R5bGU9InZpc2liaWxpdHk6IGhpZGRlbjsgd2lkdGg6IDA7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PC90aD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj48L3Rib2R5PjwvdGFibGU+PC90aD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj48L3Rib2R5PjwvdGFibGU+Cgo8L3RkPgogICAgICAgICAgICAgICAgPC90cj48L3Rib2R5PjwvdGFibGU+PC9jZW50ZXI+CiAgICAgICAgPC90ZD4KICAgICAgPC90cj48L3Rib2R5PjwvdGFibGU+Cgo8L2JvZHk+PC9odG1sPgoKCg==";
+ private final String encodedTemplateTEXT = "e3tHQVRFV0FZX05BTUV9fQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCkhpIHt7UkVRVUVTVElOR19VU0VSX0ZJUlNUX05BTUV9fSwKCnt7VVNFUl9GVUxMTkFNRX19IGhhcyBqdXN0IHJlbW92ZWQgeW91IGZyb20gdGhlIHt7U0VMRUNURURfVEVBTV9OQU1FfX0gZ3JvdXAgaW4gdGhlIHt7U0VMRUNURURfVlJFX05BTUV9fSBWUkUuCgo=";
+
+ private GCubeGroup theRequestedVRE;
+ private GCubeTeam teamToBeAssociatedWith;
+ private GCubeUser theRequestingUser;
+ private GCubeUser theManagerUser;
+ private Date originalRequestDate;
+ private String vreURL;
+
+ /**
+ *
+ * @param theRequestingUser an instance of @see {@link GCubeUser} representing the user who requested access
+ * @param teamToBeAssociatedWith an instance of @see {@link GCubeTeam} representing the team the user will join
+ * @param theManagerUser an instance of @see {@link GCubeUser} representing the manager who approved the request
+ * @param theRequestedVRE instance of @see {@link GCubeGroup} of the current VRE
+ * @param originalRequestDate the request date as in the associated {@link GCubeMembershipRequest}
+ * @param gatewayName gateway name can be obtained with {@link PortalContext#getGatewayName(javax.servlet.http.HttpServletRequest)}
+ * @param gatewayURL gateway URL name can be obtained with {@link PortalContext#getGatewayURL(javax.servlet.http.HttpServletRequest)}
+ */
+
+ public TemplateUserHasBeenDissAssociatedWithGCubeTeam(
+ GCubeGroup theRequestedVRE, GCubeTeam teamToBeAssociatedWith, GCubeUser theRequestingUser,
+ GCubeUser theManagerUser, Date originalRequestDate,
+ String gatewayName, String gatewayURL) {
+
+ super(gatewayName, gatewayURL);
+
+ this.theRequestingUser = theRequestingUser;
+ this.theManagerUser = theManagerUser;
+ this.theRequestedVRE = theRequestedVRE;
+ this.originalRequestDate = originalRequestDate;
+ this.teamToBeAssociatedWith = teamToBeAssociatedWith;
+ this.vreURL = new StringBuffer(gatewayURL)
+ .append(GCubePortalConstants.PREFIX_GROUP_URL)
+ .append("/").append(theRequestedVRE.getGroupName().toLowerCase()).toString();
+ }
+
+ @Override
+ public String compile(String templateContent) {
+ String userProfileLink = new StringBuffer(vreURL)
+ .append("/").append(getUserProfileLink(theManagerUser.getUsername())).toString();
+
+ return new String(Base64.getDecoder().decode(templateContent))
+ .replace("{{REQUESTING_USER_FIRST_NAME}}", theRequestingUser.getFirstName())
+ .replace("{{GATEWAY_LOGO:URL}}", getGatewayLogoURL())
+ .replace("{{GATEWAY_NAME}}", getGatewayName())
+ .replace("{{USER_FULLNAME}}", theManagerUser.getFullname())
+ .replace("{{SELECTED_VRE_NAME}}", theRequestedVRE.getGroupName())
+ .replace("{{VRE_URL}}", vreURL)
+ .replace("{{REQUESTING_USER_EMAIL}}", theRequestingUser.getEmail())
+ .replace("{{MANAGE_REQUEST_DATE}}", originalRequestDate.toString())
+ .replace("{{USER_VREMEMBER_PROFILE_URL}}", userProfileLink)
+ .replace("{{SELECTED_TEAM_NAME}}", teamToBeAssociatedWith.getTeamName());
+ }
+
+ @Override
+ public String getTextHTML() {
+ return compile(encodedTemplateHTML);
+ }
+
+ @Override
+ public String getTextPLAIN() {
+ return compile(encodedTemplateTEXT);
+ }
+
+ private String getUserProfileLink(String username) {
+ return "profile?"+ new String(
+ Base64.getEncoder().encodeToString(Constants.USER_PROFILE_OID.getBytes())+
+ "="+
+ new String( Base64.getEncoder().encodeToString(username.getBytes()) )
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/mail/templates/TemplateUserRolesModifiedForGroup.java b/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/mail/templates/TemplateUserRolesModifiedForGroup.java
new file mode 100644
index 0000000..726c649
--- /dev/null
+++ b/src/main/java/gr/cite/bluebridge/portlets/admin/usersmanagementportlet/mail/templates/TemplateUserRolesModifiedForGroup.java
@@ -0,0 +1,160 @@
+package gr.cite.bluebridge.portlets.admin.usersmanagementportlet.mail.templates;
+
+import org.gcube.common.portal.GCubePortalConstants;
+import org.gcube.common.portal.PortalContext;
+import org.gcube.common.portal.mailing.templates.AbstractTemplate;
+import org.gcube.common.portal.mailing.templates.Template;
+import org.gcube.portal.mailing.message.Constants;
+import org.gcube.vomanagement.usermanagement.model.*;
+
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+public class TemplateUserRolesModifiedForGroup extends AbstractTemplate implements Template {
+ private enum FormatType{
+ TEXT, HTML
+ }
+ private final String hiddenSection = "none";
+ private final String visibleSection = "block";
+ private final static String encodedTemplateHTML = "PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBYSFRNTCAxLjAgU3RyaWN0Ly9FTiIgImh0dHA6Ly93d3cudzMub3JnL1RSL3hodG1sMS9EVEQveGh0bWwxLXN0cmljdC5kdGQiPgo8aHRtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJtaW4taGVpZ2h0OiAxMDAlOyBiYWNrZ3JvdW5kLWNvbG9yOiAjZjNmM2YzICFpbXBvcnRhbnQ7Ij4KICA8aGVhZD4KICAgIDxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04IiAvPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCIgLz4KICAgIDx0aXRsZT5UaXRsZTwvdGl0bGU+CiAgPC9oZWFkPgoKPGJvZHkgc3R5bGU9IndpZHRoOiAxMDAlICFpbXBvcnRhbnQ7IG1pbi13aWR0aDogMTAwJTsgLXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OiAxMDAlOyAtbXMtdGV4dC1zaXplLWFkanVzdDogMTAwJTsgLW1vei1ib3gtc2l6aW5nOiBib3JkZXItYm94OyAtd2Via2l0LWJveC1zaXppbmc6IGJvcmRlci1ib3g7IGJveC1zaXppbmc6IGJvcmRlci1ib3g7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBiYWNrZ3JvdW5kLWNvbG9yOiAjZjNmM2YzICFpbXBvcnRhbnQ7IG1hcmdpbjogMDsgcGFkZGluZzogMDsiIGJnY29sb3I9IiNmM2YzZjMgIWltcG9ydGFudCI+ICAKICA8dGFibGUgY2xhc3M9ImJvZHkiIGRhdGEtbWFkZS13aXRoLWZvdW5kYXRpb249IiIgc3R5bGU9ImJvcmRlci1zcGFjaW5nOiAwOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBiYWNrZ3JvdW5kLWNvbG9yOiAjZjNmM2YzICFpbXBvcnRhbnQ7IGhlaWdodDogMTAwJTsgd2lkdGg6IDEwMCU7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBiZ2NvbG9yPSIjZjNmM2YzICFpbXBvcnRhbnQiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIGNsYXNzPSJmbG9hdC1jZW50ZXIiIGFsaWduPSJjZW50ZXIiIHZhbGlnbj0idG9wIiBzdHlsZT0id29yZC13cmFwOiBicmVhay13b3JkOyAtd2Via2l0LWh5cGhlbnM6IGF1dG87IC1tb3otaHlwaGVuczogYXV0bzsgaHlwaGVuczogYXV0bzsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZSAhaW1wb3J0YW50OyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBjZW50ZXI7IGZsb2F0OiBub25lOyBjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwIGF1dG87IHBhZGRpbmc6IDA7Ij4KICAgICAgICAgIDxjZW50ZXIgZGF0YS1wYXJzZWQ9IiIgc3R5bGU9IndpZHRoOiAxMDAlOyBtaW4td2lkdGg6IDU4MHB4OyI+CiAgICAgICAgICAgIDx0YWJsZSBhbGlnbj0iY2VudGVyIiBjbGFzcz0id3JhcHBlciBoZWFkZXIgZmxvYXQtY2VudGVyIiBzdHlsZT0id2lkdGg6IDEwMCU7IGJvcmRlci1zcGFjaW5nOiAwOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBjZW50ZXI7IGZsb2F0OiBub25lOyBtYXJnaW46IDAgYXV0bzsgcGFkZGluZzogMDsiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIGNsYXNzPSJ3cmFwcGVyLWlubmVyIiBzdHlsZT0id29yZC13cmFwOiBicmVhay13b3JkOyAtd2Via2l0LWh5cGhlbnM6IGF1dG87IC1tb3otaHlwaGVuczogYXV0bzsgaHlwaGVuczogYXV0bzsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZSAhaW1wb3J0YW50OyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiIHZhbGlnbj0idG9wIj4KICAgICAgICAgICAgICAgICAgPHRhYmxlIGFsaWduPSJjZW50ZXIiIGNsYXNzPSJjb250YWluZXIiIHN0eWxlPSJib3JkZXItc3BhY2luZzogMDsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogaW5oZXJpdDsgd2lkdGg6IDU4MHB4OyBiYWNrZ3JvdW5kOiAjZmVmZWZlOyBtYXJnaW46IDAgYXV0bzsgcGFkZGluZzogMDsiIGJnY29sb3I9IiNmZWZlZmUiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIHN0eWxlPSJ3b3JkLXdyYXA6IGJyZWFrLXdvcmQ7IC13ZWJraXQtaHlwaGVuczogYXV0bzsgLW1vei1oeXBoZW5zOiBhdXRvOyBoeXBoZW5zOiBhdXRvOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlICFpbXBvcnRhbnQ7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0icm93IGNvbGxhcHNlIiBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwb3NpdGlvbjogcmVsYXRpdmU7IGRpc3BsYXk6IHRhYmxlOyBwYWRkaW5nOiAwOyI+PHRib2R5Pjx0ciBzdHlsZT0idmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij48dGggY2xhc3M9InNtYWxsLTYgbGFyZ2UtNiBjb2x1bW5zIGZpcnN0IiBzdHlsZT0id2lkdGg6IDI5OHB4OyBjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwIGF1dG87IHBhZGRpbmc6IDAgMCAxNnB4OyIgYWxpZ249ImxlZnQiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIHN0eWxlPSJib3JkZXItc3BhY2luZzogMDsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgd2lkdGg6IDEwMCU7IHBhZGRpbmc6IDA7Ij48dGJvZHk+PHRyIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPjx0aCBzdHlsZT0iY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij4gPGltZyBzcmM9Int7R0FURVdBWV9MT0dPOlVSTH19IiBzdHlsZT0id2lkdGg6IDIwMHB4OyBvdXRsaW5lOiBub25lOyB0ZXh0LWRlY29yYXRpb246IG5vbmU7IC1tcy1pbnRlcnBvbGF0aW9uLW1vZGU6IGJpY3ViaWM7IG1heC13aWR0aDogMTAwJTsgY2xlYXI6IGJvdGg7IGRpc3BsYXk6IGJsb2NrOyIgYWx0PSJ7e0dBVEVXQVlfTkFNRX19IiB0aXRsZT0ie3tHQVRFV0FZX05BTUV9fSI+PC90aD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj48L3Rib2R5PjwvdGFibGU+PC90aD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGggY2xhc3M9InNtYWxsLTYgbGFyZ2UtNiBjb2x1bW5zIGxhc3QiIHN0eWxlPSJ3aWR0aDogMjk4cHg7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDAgYXV0bzsgcGFkZGluZzogMCAwIDE2cHg7IiBhbGlnbj0ibGVmdCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgc3R5bGU9ImJvcmRlci1zcGFjaW5nOiAwOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyB3aWR0aDogMTAwJTsgcGFkZGluZzogMDsiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRoIHN0eWxlPSJjb2xvcjogIzBhMGEwYTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IGZvbnQtc2l6ZTogMTZweDsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHAgY2xhc3M9InRleHQtcmlnaHQiIHN0eWxlPSJ0ZXh0LWFsaWduOiByaWdodDsgY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMCAwIDEwcHg7IHBhZGRpbmc6IDA7IiBhbGlnbj0icmlnaHQiPjwvcD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT48L3RoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT48L3RkPgogICAgICAgICAgICAgICAgICAgICAgPC90cj48L3Rib2R5PjwvdGFibGU+PC90ZD4KICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT48dGFibGUgYWxpZ249ImNlbnRlciIgY2xhc3M9ImNvbnRhaW5lciBib2R5LWJvcmRlciBmbG9hdC1jZW50ZXIiIHN0eWxlPSJib3JkZXItc3BhY2luZzogMDsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogY2VudGVyOyB3aWR0aDogNTgwcHg7IGZsb2F0OiBub25lOyBib3JkZXItdG9wLXdpZHRoOiA4cHg7IGJvcmRlci10b3AtY29sb3I6ICMyMjVmOTc7IGJvcmRlci10b3Atc3R5bGU6IHNvbGlkOyBiYWNrZ3JvdW5kOiAjZmVmZWZlOyBtYXJnaW46IDAgYXV0bzsgcGFkZGluZzogMDsiIGJnY29sb3I9IiNmZWZlZmUiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIHN0eWxlPSJ3b3JkLXdyYXA6IGJyZWFrLXdvcmQ7IC13ZWJraXQtaHlwaGVuczogYXV0bzsgLW1vei1oeXBoZW5zOiBhdXRvOyBoeXBoZW5zOiBhdXRvOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlICFpbXBvcnRhbnQ7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0icm93IiBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwb3NpdGlvbjogcmVsYXRpdmU7IGRpc3BsYXk6IHRhYmxlOyBwYWRkaW5nOiAwOyI+PHRib2R5Pjx0ciBzdHlsZT0idmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij48dGggY2xhc3M9InNtYWxsLTEyIGxhcmdlLTEyIGNvbHVtbnMgZmlyc3QgbGFzdCIgc3R5bGU9IndpZHRoOiA1NjRweDsgY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMCBhdXRvOyBwYWRkaW5nOiAwIDE2cHggMTZweDsiIGFsaWduPSJsZWZ0Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBzdHlsZT0iYm9yZGVyLXNwYWNpbmc6IDA7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IHZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHdpZHRoOiAxMDAlOyBwYWRkaW5nOiAwOyI+PHRib2R5Pjx0ciBzdHlsZT0idmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij48dGggc3R5bGU9ImNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgZm9udC1zaXplOiAxNnB4OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgY2xhc3M9InNwYWNlciIgc3R5bGU9ImJvcmRlci1zcGFjaW5nOiAwOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOyB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyB3aWR0aDogMTAwJTsgcGFkZGluZzogMDsiPjx0Ym9keT48dHIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+PHRkIGhlaWdodD0iMTZweCIgc3R5bGU9ImZvbnQtc2l6ZTogMTZweDsgbGluZS1oZWlnaHQ6IDE2cHg7IHdvcmQtd3JhcDogYnJlYWstd29yZDsgLXdlYmtpdC1oeXBoZW5zOiBhdXRvOyAtbW96LWh5cGhlbnM6IGF1dG87IGh5cGhlbnM6IGF1dG87IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2UgIWltcG9ydGFudDsgdmVydGljYWwtYWxpZ246IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgbXNvLWxpbmUtaGVpZ2h0LXJ1bGU6IGV4YWN0bHk7IGNvbG9yOiAjMGEwYTBhOyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiIHZhbGlnbj0idG9wIj4mbmJzcDs8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+PC90Ym9keT48L3RhYmxlPgoJCQkJCQkJCQkgIDxoNCBzdHlsZT0iY29sb3I6IGluaGVyaXQ7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyB3b3JkLXdyYXA6IG5vcm1hbDsgZm9udC1zaXplOiAyNHB4OyBtYXJnaW46IDAgMCAxMHB4OyBwYWRkaW5nOiAwOyIgYWxpZ249ImxlZnQiPgoJCQkJCQkJCQkJICBIaSB7e1JFUVVFU1RJTkdfVVNFUl9GSVJTVF9OQU1FfX0sCgkJCQkJCQkJCQkgIDxicj4KCQkJCQkJCQkJCSAgPHNwYW4gc3R5bGU9ImRpc3BsYXk6e3tWSVNJQklMSVRZX09GX05FV19ST0xFU19TRUNUSU9OfX07Ij48YSBocmVmPSJ7e1VTRVJfVlJFTUVNQkVSX1BST0ZJTEVfVVJMfX0iIHN0eWxlPSJjb2xvcjogIzIxOTllODsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyI+CgkJCQkJCQkJCQkgIHt7VVNFUl9GVUxMTkFNRX19CgkJCQkJCQkJCQkgIDwvYT4gaGFzIGp1c3QgYXNzaWduZWQgeW91IHRoZSBmb2xsb3dpbmcgcm9sZXM6IHt7TkVXX1JPTEVTfX0gaW4gdGhlIAoJCQkJCQkJCQkJICA8YSBocmVmPSJ7e1ZSRV9VUkx9fSIgc3R5bGU9ImNvbG9yOiAjMjE5OWU4OyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgdGV4dC1kZWNvcmF0aW9uOiBub25lOyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7Ij4KCQkJCQkJCQkJCSAge3tTRUxFQ1RFRF9WUkVfTkFNRX19CgkJCQkJCQkJCQkgIDwvYT4gVlJFLgo8L3NwYW4+CgkJCQkJCQkJCSAgPC9oND4KCQkJCQkJCQkJICA8aDQgc3R5bGU9ImNvbG9yOiBpbmhlcml0OyBkaXNwbGF5Ont7VklTSUJJTElUWV9PRl9SRVZPS0VEX1JPTEVTX1NFQ1RJT059fTsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IHdvcmQtd3JhcDogbm9ybWFsOyBmb250LXNpemU6IDI0cHg7IG1hcmdpbjogMCAwIDEwcHg7IHBhZGRpbmc6IDA7IiBhbGlnbj0ibGVmdCI+CgkJCQkJCQkJCQkgIDxhIGhyZWY9Int7VVNFUl9WUkVNRU1CRVJfUFJPRklMRV9VUkx9fSIgc3R5bGU9ImNvbG9yOiAjMjE5OWU4OyBmb250LWZhbWlseTogSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgdGV4dC1hbGlnbjogbGVmdDsgbGluZS1oZWlnaHQ6IDEuMzsgdGV4dC1kZWNvcmF0aW9uOiBub25lOyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7Ij4KCQkJCQkJCQkJCSAge3tVU0VSX0ZVTExOQU1FfX0KCQkJCQkJCQkJCSAgPC9hPiBoYXMganVzdCByZXZva2VkIHlvdXIgcm9sZXM6IHt7UkVWT0tFRF9ST0xFU319IGluIHRoZSAKCQkJCQkJCQkJCSAgPGEgaHJlZj0ie3tWUkVfVVJMfX0iIHN0eWxlPSJjb2xvcjogIzIxOTllODsgZm9udC1mYW1pbHk6IEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtYWxpZ246IGxlZnQ7IGxpbmUtaGVpZ2h0OiAxLjM7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyI+CgkJCQkJCQkJCQkgIHt7U0VMRUNURURfVlJFX05BTUV9fQoJCQkJCQkJCQkJICA8L2E+IFZSRS4KCgkJCQkJCQkJCSAgPC9oND4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90aD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGggY2xhc3M9ImV4cGFuZGVyIiBzdHlsZT0idmlzaWJpbGl0eTogaGlkZGVuOyB3aWR0aDogMDsgY29sb3I6ICMwYTBhMGE7IGZvbnQtZmFtaWx5OiBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWFsaWduOiBsZWZ0OyBsaW5lLWhlaWdodDogMS4zOyBmb250LXNpemU6IDE2cHg7IG1hcmdpbjogMDsgcGFkZGluZzogMDsiIGFsaWduPSJsZWZ0Ij48L3RoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT48L3RoPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT4KCjwvdGQ+CiAgICAgICAgICAgICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT48L2NlbnRlcj4KICAgICAgICA8L3RkPgogICAgICA8L3RyPjwvdGJvZHk+PC90YWJsZT4KPC9ib2R5PjwvaHRtbD4K";
+ private final static String encodedTemplateTEXT = "e3tHQVRFV0FZX05BTUV9fQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCkhpIHt7UkVRVUVTVElOR19VU0VSX0ZJUlNUX05BTUV9fSwKCnt7VVNFUl9GVUxMTkFNRX19IGhhcyBqdXN0IGFzc2lnbmVkIHlvdSB0aGUgcm9sZXMge3tORVdfUk9MRVN9fSBpbiB0aGUge3tTRUxFQ1RFRF9WUkVfTkFNRX19IFZSRS4KCnt7VVNFUl9GVUxMTkFNRX19IGhhcyBqdXN0IHJldm9rZWQgeW91ciByb2xlcyB7e1JFVk9LRURfUk9MRVN9fSBpbiB0aGUge3tTRUxFQ1RFRF9WUkVfTkFNRX19IFZSRS4=";
+ private final static String encodedTemplateTEXTGreeting = "e3tHQVRFV0FZX05BTUV9fQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCkhpIHt7UkVRVUVTVElOR19VU0VSX0ZJUlNUX05BTUV9fSwK";
+ private final static String encodedTemplateTEXTAssignedRoles = "Cnt7VVNFUl9GVUxMTkFNRX19IGhhcyBqdXN0IGFzc2lnbmVkIHlvdSB0aGUgcm9sZXMge3tORVdfUk9MRVN9fSBpbiB0aGUge3tTRUxFQ1RFRF9WUkVfTkFNRX19IFZSRS4K";
+ private final static String encodedTemplateTEXTRevokedRoles = "Cnt7VVNFUl9GVUxMTkFNRX19IGhhcyBqdXN0IHJldm9rZWQgeW91ciByb2xlcyB7e1JFVk9LRURfUk9MRVN9fSBpbiB0aGUge3tTRUxFQ1RFRF9WUkVfTkFNRX19IFZSRS4=";
+
+ private FormatType formatType;
+
+ private GCubeGroup theRequestedVRE;
+ private GCubeUser theRequestingUser;
+ private GCubeUser theManagerUser;
+ private List newRoles;
+ private List rolesRevoked;
+ private Date originalRequestDate;
+ private String vreURL;
+
+ /**
+ *
+ * @param theRequestingUser an instance of @see {@link GCubeUser} representing the user who requested access
+ * @param theManagerUser an instance of @see {@link GCubeUser} representing the manager who approved the request
+ * @param theRequestedVRE instance of @see {@link GCubeGroup} of the current VRE
+ * @param originalRequestDate the request date as in the associated {@link GCubeMembershipRequest}
+ * @param gatewayName gateway name can be obtained with {@link PortalContext#getGatewayName(javax.servlet.http.HttpServletRequest)}
+ * @param gatewayURL gateway URL name can be obtained with {@link PortalContext#getGatewayURL(javax.servlet.http.HttpServletRequest)}
+ * @param newRoles a collection of instances of @see {@link GCubeRole} representing the collection of new roles the user will be assigned
+ * @param rolesRevoked a collection of instances of @see {@link GCubeRole} representing the collection of roles that will be revoked
+ */
+
+ public TemplateUserRolesModifiedForGroup(String gatewayName, String gatewayURL, GCubeGroup theRequestedVRE,
+ GCubeUser theRequestingUser, GCubeUser theManagerUser, List newRoles,
+ List rolesRevoked, Date originalRequestDate) {
+ super(gatewayName, gatewayURL);
+
+ this.theRequestedVRE = theRequestedVRE;
+ this.theRequestingUser = theRequestingUser;
+ this.theManagerUser = theManagerUser;
+ this.newRoles = newRoles;
+ this.rolesRevoked = rolesRevoked;
+ this.originalRequestDate = originalRequestDate;
+ this.vreURL = new StringBuffer(gatewayURL)
+ .append(GCubePortalConstants.PREFIX_GROUP_URL)
+ .append("/").append(theRequestedVRE.getGroupName().toLowerCase()).toString();
+ }
+
+ @Override
+ public String compile(String templateContent) {
+ String userProfileLink = new StringBuffer(vreURL)
+ .append("/").append(getUserProfileLink(theManagerUser.getUsername())).toString();
+
+ return new String(Base64.getDecoder().decode(templateContent))
+ .replace("{{REQUESTING_USER_FIRST_NAME}}", theRequestingUser.getFirstName())
+ .replace("{{GATEWAY_LOGO:URL}}", getGatewayLogoURL())
+ .replace("{{GATEWAY_NAME}}", getGatewayName())
+ .replace("{{USER_FULLNAME}}", theManagerUser.getFullname())
+ .replace("{{SELECTED_VRE_NAME}}", theRequestedVRE.getGroupName())
+ .replace("{{VRE_URL}}", vreURL)
+ .replace("{{REQUESTING_USER_EMAIL}}", theRequestingUser.getEmail())
+ .replace("{{MANAGE_REQUEST_DATE}}", originalRequestDate.toString())
+ .replace("{{VISIBILITY_OF_NEW_ROLES_SECTION}}", this.displayVisibilityOfRolesSectionBasedOnRolesSize(newRoles))
+ .replace("{{NEW_ROLES}}", this.getRoleNamesFromRoles(newRoles))
+ .replace("{{USER_VREMEMBER_PROFILE_URL}}", userProfileLink)
+ .replace("{{VISIBILITY_OF_REVOKED_ROLES_SECTION}}", this.displayVisibilityOfRolesSectionBasedOnRolesSize(rolesRevoked))
+ .replace("{{REVOKED_ROLES}}", this.getRoleNamesFromRoles(rolesRevoked));
+ }
+
+ @Override
+ public String getTextHTML() {
+ this.formatType = FormatType.HTML;
+ return compile(encodedTemplateHTML);
+ }
+
+ @Override
+ public String getTextPLAIN() {
+ this.formatType = FormatType.TEXT;
+ return compile( this.buildTextPlainBasedOnRoles() );
+ }
+
+ private String buildTextPlainBasedOnRoles() {
+ StringBuilder finalTextBuilder = new StringBuilder(encodedTemplateTEXTGreeting);
+
+ if(!this.newRoles.isEmpty())
+ finalTextBuilder.append(encodedTemplateTEXTAssignedRoles);
+
+ if(!this.rolesRevoked.isEmpty())
+ finalTextBuilder.append(encodedTemplateTEXTRevokedRoles);
+
+ return finalTextBuilder.toString();
+ }
+
+ private String getUserProfileLink(String username) {
+ return "profile?"+ new String(
+ Base64.getEncoder().encodeToString(Constants.USER_PROFILE_OID.getBytes())+
+ "="+
+ new String( Base64.getEncoder().encodeToString(username.getBytes()) )
+ );
+ }
+
+ private String getRoleNamesFromRoles(Collection roles) {
+ if(this.formatType == FormatType.HTML)
+ return this.buildRoleSectionForHTMLFormat(roles);
+
+ if(this.formatType == FormatType.TEXT)
+ return this.buildRoleSectionForTextFormat(roles);
+
+ return "";
+ }
+
+ private String buildRoleSectionForTextFormat(Collection roles) {
+ StringBuilder sb = new StringBuilder();
+ if(roles == null || roles.isEmpty())
+ return sb.append("-").toString();
+
+ boolean first = true;
+ for(GCubeRole role : roles) {
+ StringBuilder prefix = first ? new StringBuilder() : new StringBuilder(", ");
+ sb.append(prefix);
+ sb.append(role.getRoleName());
+ first = false;
+ }
+
+ return sb.toString();
+ }
+
+ private String buildRoleSectionForHTMLFormat(Collection roles) {
+ if(roles == null)
+ return " -";
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("");
+ roles.forEach(r -> sb.append("" + r.getRoleName() + " "));
+ sb.append(" ");
+
+ return sb.toString();
+ }
+
+ private String displayVisibilityOfRolesSectionBasedOnRolesSize(Collection roles) {
+ return roles == null || roles.isEmpty() ? this.hiddenSection : this.visibleSection;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/EmailTemplates.properties b/src/main/resources/EmailTemplates.properties
new file mode 100644
index 0000000..88c9526
--- /dev/null
+++ b/src/main/resources/EmailTemplates.properties
@@ -0,0 +1,10 @@
+membershipRequestAcceptanceBody=Dear {0} ,
Your request for accessing the {1} has been approved by {2}
From now on you can access it here: {5} using the following email: {4}
+membershipRequestAcceptanceSubject={0} Request Approved
+membershipRequestRejectionBody=Dear {0} ,
Your request for accessing the {1} at: {5} has been rejected by {2}
+membershipRequestRejectionSubject={0} Request Rejected
+userDismissalFromSiteBody=You have been unregistered from the {0} VO/VRE
The {0} VO/VRE administration team
+userDismissalFromSiteSubject={0} You have been unregistered
+siteTeamAssignmentSubject=You are now associated with {0}
+siteTeamDismissalSubject=You have been dismissed from {0}
+roleAssignmentRevokeSubject=Your roles have been modified for {0}
+
diff --git a/src/main/webapp/WEB-INF/liferay-display.xml b/src/main/webapp/WEB-INF/liferay-display.xml
new file mode 100644
index 0000000..2673649
--- /dev/null
+++ b/src/main/webapp/WEB-INF/liferay-display.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/liferay-plugin-package.properties b/src/main/webapp/WEB-INF/liferay-plugin-package.properties
new file mode 100644
index 0000000..b50a28f
--- /dev/null
+++ b/src/main/webapp/WEB-INF/liferay-plugin-package.properties
@@ -0,0 +1,10 @@
+name=UsersManagementPortlet
+module-group-id=liferay
+module-incremental-version=1
+tags=
+short-description=
+change-log=
+page-url=http://www.liferay.com
+author=Liferay, Inc.
+licenses=LGPL
+portal.dependency.jars=usermanagement-core-2.0.0-20160229.000514-14.jar
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/liferay-portlet.xml b/src/main/webapp/WEB-INF/liferay-portlet.xml
new file mode 100644
index 0000000..403250d
--- /dev/null
+++ b/src/main/webapp/WEB-INF/liferay-portlet.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Users Management
+ /icon.png
+
+
+ administrator
+ Administrator
+
+
+ guest
+ Guest
+
+
+ power-user
+ Power User
+
+
+ user
+ User
+
+
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/portlet.xml b/src/main/webapp/WEB-INF/portlet.xml
new file mode 100644
index 0000000..501196d
--- /dev/null
+++ b/src/main/webapp/WEB-INF/portlet.xml
@@ -0,0 +1,42 @@
+
+
+
+
+ Users Management
+ Users Management
+ gr.cite.bluebridge.portlets.admin.usersmanagementportlet.UsersManagementPortletHome
+
+ view-template
+ /views/
+
+
+ 0
+
+ text/html
+
+
+ Users Management
+ Users Management
+ Users Management
+
+
+ administrator
+
+
+ guest
+
+
+ power-user
+
+
+ user
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..7d8c082
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/css/Tags.css b/src/main/webapp/css/Tags.css
new file mode 100644
index 0000000..ba30afd
--- /dev/null
+++ b/src/main/webapp/css/Tags.css
@@ -0,0 +1,142 @@
+div#usersManagementPortletContainer div.modal-header,
+div#usersManagementPortletContainer div.modal-footer{
+ border: 0px !important;
+}
+div#usersManagementPortletContainer div.text-tag.span2 {
+ padding : 0px;
+}
+div#usersManagementPortletContainer div#border,
+div#usersManagementPortletContainer div#borderFirstScreen {
+ /* height:3px; */
+ border-top: 2px solid #0271be;
+/* padding-bottom: 0.2em; */
+}
+div#usersManagementPortletContainer div.text-tag.span2:not(:first-of-type) {
+ margin-left : 10px !important;
+}
+div#usersManagementPortletContainer div.text-wrap {
+ width :inherit !important;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row div.text-core.span9 div.text-wrap,
+ div#usersManagementPortletContainer span#textAboveTagsInputInAssignRolesModal div.row div.text-core.span9 div.text-wrap,
+ div#usersManagementPortletContainer span#textAboveTagsInputInAssignUsersToGroupsModal div.row div.text-core.span9 div.text-wrap {
+ width :100% !important;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row div.text-core.span9,
+ div#usersManagementPortletContainer span#textAboveTagsInputInAssignRolesModal div.row div.text-core.span9,
+ div#usersManagementPortletContainer span#textAboveTagsInputInAssignUsersToGroupsModal div.row div.text-core.span9 {
+ min-height: 45px !important;
+ margin-top : -10px;
+ height:100% !important;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row:first div.text-core.span9 {
+ position: static;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row div.text-core.span9 div.text-wrap ,
+ div#usersManagementPortletContainer span#textAboveTagsInputInAssignRolesModal div.row div.text-core.span9 div.text-wrap,
+ div#usersManagementPortletContainer span#textAboveTagsInputInAssignUsersToGroupsModal div.row div.text-core.span9 div.text-wrap {
+ position:static;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row div.text-core.span9 div.text-wrap div.text-tags,
+div#usersManagementPortletContainer span#textAboveTagsInputInAssignRolesModal div.row div.text-core.span9 div.text-wrap div.text-tags,
+div#usersManagementPortletContainer span#textAboveTagsInputInAssignUsersToGroupsModal div.row div.text-core.span9 div.text-wrap div.text-tags{
+ position:static;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row div.text-core.span9 div.text-button {
+ padding: 0px 0px 8px 5px !important;
+ text-align: -webkit-center;
+}
+div#usersManagementPortletContainer #assignUsersRolesModal div.text-button.span12,
+ div#usersManagementPortletContainer #assignUsersToGroupsModal div.text-button.span12 {
+ padding-right: 0px;
+}
+div#usersManagementPortletContainer div.text-button.span12{
+ padding-left:10px !important;
+ border-radius : 6px !important;
+}
+div#usersManagementPortletContainer a.tag-remove {
+ /* margin-left : 5px; */
+ font-size : 14px;
+ padding: 3px 0px 3px 2px;
+ /* margin-top : 4px; */
+}
+div#usersManagementPortletContainer div#requestsAcceptanceBody > div.row:first-of-type > div.text-core > div.text-wrap > div.text-tags > div.text-tag:not(:last-of-type){
+ margin-right : 15px !important;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput> div.row div.text-tags > div.text-tag:not(:last-of-type),
+div#usersManagementPortletContainer span#textAboveTagsInputInAssignRolesModal> div.row div.text-tags > div.text-tag:not(:last-of-type),
+div#usersManagementPortletContainer span#textAboveTagsInputInAssignUsersToGroupsModal> div.row div.text-tags > div.text-tag:not(:last-of-type){
+ margin-right : 15px !important;
+}
+div#usersManagementPortletContainer a.tag-remove:hover {
+ /* background : white;
+ background-color : white; */
+ text-decoration : none;
+ color: red;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row:nth-last-child(-n+2) div.text-core.span9 div.text-wrap div.text-dropdown.text-position-below,
+div#usersManagementPortletContainer span#textAboveTagsInputInAssignRolesModal div.row:nth-last-child(-n+2) div.text-core.span9 div.text-wrap div.text-dropdown.text-position-below,
+div#usersManagementPortletContainer span#textAboveTagsInputInAssignUsersToGroupsModal div.row:nth-last-child(-n+2) div.text-core.span9 div.text-wrap div.text-dropdown.text-position-below {
+ margin-left: -5px;
+ top: 100% !important;
+}
+div#usersManagementPortletContainer textarea#userNamesList {
+ width :100% !important;
+ display : none !important;
+}
+div#usersManagementPortletContainer textarea#userNamesListInAssignRolesModal,
+div#usersManagementPortletContainer textarea#userNamesListInAssignUsersToGroupsModal {
+ width :100% !important;
+ display : none !important;
+}
+div#usersManagementPortletContainer div.text-tags {
+ padding-top:0px !important;
+ padding-bottom:0px !important;
+ padding-left : 0px !important;
+}
+div#usersManagementPortletContainer div.text-button {
+ background : rgb(234,234,234) !important;
+ background-color : rgb(234,234,234) !important;
+ border : 0px !important;
+ text-align :
+}
+div#usersManagementPortletContainer div.text-button span {
+ padding-top : 7px;
+ display : inline-block;
+ text-align: center;
+ font-size : 14px;
+ color: #0271be;
+ white-space : nowrap;
+}
+div.modal-body div.row ~ div.row div.text-button span {
+ /* text-transform : capitalize; */
+}
+div#usersManagementPortletContainer a.text-remove {
+ display : none !important;
+}
+div#usersManagementPortletContainer div.text-button.span12 {
+ border-radius : 7px;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceBody > div.row > div.text-core.span11 > div.text-wrap {
+ position: static !important;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceBody > div.row > div.text-core.span11 > div.text-wrap > div.text-tags{
+ position : static;
+}
+div#usersManagementPortletContainer input#singleTag {
+ margin-top : 0px;
+ margin-left : 10px;
+}
+div#usersManagementPortletContainer div#singleTagSection.hiddenSection,
+div#usersManagementPortletContainer div#multipleTagsSection.hiddenSection,
+div#usersManagementPortletContainer div#singleTagSectionInAssignRolesModal.hiddenSection,
+div#usersManagementPortletContainer div#multipleTagsSectionInAssignRolesModal.hiddenSection,
+div#usersManagementPortletContainer div#singleTagSectionInAssignUsersToGroupsModal.hiddenSection,
+div#usersManagementPortletContainer div#multipleTagsSectionInAssignUsersToGroupsModal.hiddenSection{
+ display :none;
+}
+.recipients .text-tag,
+.emailCBCContainer .text-tag {
+ margin-right: 1em;
+ margin-bottom: 0.5em;
+}
\ No newline at end of file
diff --git a/src/main/webapp/css/jquery_datatables/buttons.dataTables.min.css b/src/main/webapp/css/jquery_datatables/buttons.dataTables.min.css
new file mode 100644
index 0000000..b7c3e63
--- /dev/null
+++ b/src/main/webapp/css/jquery_datatables/buttons.dataTables.min.css
@@ -0,0 +1 @@
+div.dt-button-info{position:fixed;top:50%;left:50%;width:400px;margin-top:-100px;margin-left:-200px;background-color:white;border:2px solid #111;box-shadow:3px 3px 8px rgba(0,0,0,0.3);border-radius:3px;text-align:center;z-index:21}div.dt-button-info h2{padding:0.5em;margin:0;font-weight:normal;border-bottom:1px solid #ddd;background-color:#f3f3f3}div.dt-button-info>div{padding:1em}button.dt-button,div.dt-button,a.dt-button{position:relative;display:inline-block;box-sizing:border-box;margin-right:0.333em;padding:0.5em 1em;border:1px solid #999;border-radius:2px;cursor:pointer;font-size:0.88em;color:black;white-space:nowrap;overflow:hidden;background-color:#e9e9e9;background-image:-webkit-linear-gradient(top, #fff 0%, #e9e9e9 100%);background-image:-moz-linear-gradient(top, #fff 0%, #e9e9e9 100%);background-image:-ms-linear-gradient(top, #fff 0%, #e9e9e9 100%);background-image:-o-linear-gradient(top, #fff 0%, #e9e9e9 100%);background-image:linear-gradient(top, #fff 0%, #e9e9e9 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='white', EndColorStr='#e9e9e9');-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-decoration:none;outline:none}button.dt-button.disabled,div.dt-button.disabled,a.dt-button.disabled{color:#999;border:1px solid #d0d0d0;cursor:default;background-color:#f9f9f9;background-image:-webkit-linear-gradient(top, #fff 0%, #f9f9f9 100%);background-image:-moz-linear-gradient(top, #fff 0%, #f9f9f9 100%);background-image:-ms-linear-gradient(top, #fff 0%, #f9f9f9 100%);background-image:-o-linear-gradient(top, #fff 0%, #f9f9f9 100%);background-image:linear-gradient(top, #fff 0%, #f9f9f9 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#ffffff', EndColorStr='#f9f9f9')}button.dt-button:active:not(.disabled),button.dt-button.active:not(.disabled),div.dt-button:active:not(.disabled),div.dt-button.active:not(.disabled),a.dt-button:active:not(.disabled),a.dt-button.active:not(.disabled){background-color:#e2e2e2;background-image:-webkit-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%);background-image:-moz-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%);background-image:-ms-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%);background-image:-o-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%);background-image:linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#f3f3f3', EndColorStr='#e2e2e2');box-shadow:inset 1px 1px 3px #999999}button.dt-button:active:not(.disabled):hover:not(.disabled),button.dt-button.active:not(.disabled):hover:not(.disabled),div.dt-button:active:not(.disabled):hover:not(.disabled),div.dt-button.active:not(.disabled):hover:not(.disabled),a.dt-button:active:not(.disabled):hover:not(.disabled),a.dt-button.active:not(.disabled):hover:not(.disabled){box-shadow:inset 1px 1px 3px #999999;background-color:#cccccc;background-image:-webkit-linear-gradient(top, #eaeaea 0%, #ccc 100%);background-image:-moz-linear-gradient(top, #eaeaea 0%, #ccc 100%);background-image:-ms-linear-gradient(top, #eaeaea 0%, #ccc 100%);background-image:-o-linear-gradient(top, #eaeaea 0%, #ccc 100%);background-image:linear-gradient(top, #eaeaea 0%, #ccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#eaeaea', EndColorStr='#cccccc')}button.dt-button:hover,div.dt-button:hover,a.dt-button:hover{text-decoration:none}button.dt-button:hover:not(.disabled),div.dt-button:hover:not(.disabled),a.dt-button:hover:not(.disabled){border:1px solid #666;background-color:#e0e0e0;background-image:-webkit-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%);background-image:-moz-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%);background-image:-ms-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%);background-image:-o-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%);background-image:linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#f9f9f9', EndColorStr='#e0e0e0')}button.dt-button:focus:not(.disabled),div.dt-button:focus:not(.disabled),a.dt-button:focus:not(.disabled){border:1px solid #426c9e;text-shadow:0 1px 0 #c4def1;outline:none;background-color:#79ace9;background-image:-webkit-linear-gradient(top, #bddef4 0%, #79ace9 100%);background-image:-moz-linear-gradient(top, #bddef4 0%, #79ace9 100%);background-image:-ms-linear-gradient(top, #bddef4 0%, #79ace9 100%);background-image:-o-linear-gradient(top, #bddef4 0%, #79ace9 100%);background-image:linear-gradient(top, #bddef4 0%, #79ace9 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#bddef4', EndColorStr='#79ace9')}.dt-button embed{outline:none}div.dt-buttons{position:relative;float:left}div.dt-buttons.buttons-right{float:right}div.dt-button-collection{position:absolute;top:0;left:0;width:150px;margin-top:3px;padding:8px 8px 4px 8px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.4);background-color:white;overflow:hidden;z-index:2002;border-radius:5px;box-shadow:3px 3px 5px rgba(0,0,0,0.3);z-index:2002;-webkit-column-gap:8px;-moz-column-gap:8px;-ms-column-gap:8px;-o-column-gap:8px;column-gap:8px}div.dt-button-collection button.dt-button,div.dt-button-collection div.dt-button,div.dt-button-collection a.dt-button{position:relative;left:0;right:0;display:block;float:none;margin-bottom:4px;margin-right:0}div.dt-button-collection button.dt-button:active:not(.disabled),div.dt-button-collection button.dt-button.active:not(.disabled),div.dt-button-collection div.dt-button:active:not(.disabled),div.dt-button-collection div.dt-button.active:not(.disabled),div.dt-button-collection a.dt-button:active:not(.disabled),div.dt-button-collection a.dt-button.active:not(.disabled){background-color:#dadada;background-image:-webkit-linear-gradient(top, #f0f0f0 0%, #dadada 100%);background-image:-moz-linear-gradient(top, #f0f0f0 0%, #dadada 100%);background-image:-ms-linear-gradient(top, #f0f0f0 0%, #dadada 100%);background-image:-o-linear-gradient(top, #f0f0f0 0%, #dadada 100%);background-image:linear-gradient(top, #f0f0f0 0%, #dadada 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#f0f0f0', EndColorStr='#dadada');box-shadow:inset 1px 1px 3px #666}div.dt-button-collection.fixed{position:fixed;top:50%;left:50%;margin-left:-75px}div.dt-button-collection.fixed.two-column{margin-left:-150px}div.dt-button-collection.fixed.three-column{margin-left:-225px}div.dt-button-collection.fixed.four-column{margin-left:-300px}div.dt-button-collection>*{-webkit-column-break-inside:avoid;break-inside:avoid}div.dt-button-collection.two-column{width:300px;padding-bottom:1px;-webkit-column-count:2;-moz-column-count:2;-ms-column-count:2;-o-column-count:2;column-count:2}div.dt-button-collection.three-column{width:450px;padding-bottom:1px;-webkit-column-count:3;-moz-column-count:3;-ms-column-count:3;-o-column-count:3;column-count:3}div.dt-button-collection.four-column{width:600px;padding-bottom:1px;-webkit-column-count:4;-moz-column-count:4;-ms-column-count:4;-o-column-count:4;column-count:4}div.dt-button-background{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);background:-ms-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-moz-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-o-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-webkit-gradient(radial, center center, 0, center center, 497, color-stop(0, rgba(0,0,0,0.3)), color-stop(1, rgba(0,0,0,0.7)));background:-webkit-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:radial-gradient(ellipse farthest-corner at center, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);z-index:2001}@media screen and (max-width: 640px){div.dt-buttons{float:none !important;text-align:center}}
diff --git a/src/main/webapp/css/jquery_datatables/editor.dataTables.min.css b/src/main/webapp/css/jquery_datatables/editor.dataTables.min.css
new file mode 100644
index 0000000..e416ad6
--- /dev/null
+++ b/src/main/webapp/css/jquery_datatables/editor.dataTables.min.css
@@ -0,0 +1 @@
+div.DTE{position:relative}div.DTE div.DTE_Processing_Indicator{position:absolute;top:10px;right:13px;height:32px;width:32px;background:url("../images/ajax-loader.gif") no-repeat top left;display:none;z-index:20}div.DTE div.DTE_Header{position:absolute;top:0;left:0;height:50px;width:100%;background-color:#f3f3f3;border-bottom:1px solid #ddd;padding:16px 10px 2px 16px;font-size:1.3em;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.DTE div.DTE_Footer{position:absolute;bottom:0;left:0;height:50px;width:100%;background-color:#f3f3f3;border-top:1px solid #ddd;padding:10px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.DTE div.DTE_Form_Info{margin-bottom:0.5em;display:none}div.DTE div.DTE_Form_Content{position:relative;padding:10px}div.DTE div.DTE_Form_Error{float:left;padding:5px;display:none;color:#b11f1f}div.DTE button.btn,div.DTE div.DTE_Form_Buttons button{position:relative;text-align:center;display:block;margin-top:0;padding:5px 15px;cursor:pointer;float:right;margin-left:0.75em;font-size:14px;text-shadow:0 1px 0 white;border:1px solid #999;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;border-radius:4px;-webkit-box-shadow:1px 1px 3px #ccc;-moz-box-shadow:1px 1px 3px #ccc;box-shadow:1px 1px 3px #ccc;background-color:#f9f9f9 100%;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 65%, #f9f9f9 100%);background-image:-moz-linear-gradient(top, #fff 0%, #eee 65%, #f9f9f9 100%);background-image:-ms-linear-gradient(top, #fff 0%, #eee 65%, #f9f9f9 100%);background-image:-o-linear-gradient(top, #fff 0%, #eee 65%, #f9f9f9 100%);background-image:linear-gradient(top, #fff 0%, #eee 65%, #f9f9f9 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#ffffff', EndColorStr='#f9f9f9')}div.DTE button.btn:hover,div.DTE div.DTE_Form_Buttons button:hover{border:1px solid #666;-webkit-box-shadow:1px 1px 3px #999;-moz-box-shadow:1px 1px 3px #999;box-shadow:1px 1px 3px #999;background-color:#f4f4f4 100%;background-image:-webkit-linear-gradient(top, #f3f3f3 0%, #dbdbdb 65%, #f4f4f4 100%);background-image:-moz-linear-gradient(top, #f3f3f3 0%, #dbdbdb 65%, #f4f4f4 100%);background-image:-ms-linear-gradient(top, #f3f3f3 0%, #dbdbdb 65%, #f4f4f4 100%);background-image:-o-linear-gradient(top, #f3f3f3 0%, #dbdbdb 65%, #f4f4f4 100%);background-image:linear-gradient(top, #f3f3f3 0%, #dbdbdb 65%, #f4f4f4 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#f3f3f3', EndColorStr='#f4f4f4')}div.DTE button.btn:active,div.DTE div.DTE_Form_Buttons button:active{-webkit-box-shadow:inset 1px 1px 3px #999;-moz-box-shadow:inset 1px 1px 3px #999;box-shadow:inset 1px 1px 3px #999}div.DTE button.btn:focus,div.DTE div.DTE_Form_Buttons button:focus{border:1px solid #426c9e;text-shadow:0 1px 0 #c4def1;background-color:#a3d0ef 100%;background-image:-webkit-linear-gradient(top, #a3d0ef 0%, #79ace9 65%, #a3d0ef 100%);background-image:-moz-linear-gradient(top, #a3d0ef 0%, #79ace9 65%, #a3d0ef 100%);background-image:-ms-linear-gradient(top, #a3d0ef 0%, #79ace9 65%, #a3d0ef 100%);background-image:-o-linear-gradient(top, #a3d0ef 0%, #79ace9 65%, #a3d0ef 100%);background-image:linear-gradient(top, #a3d0ef 0%, #79ace9 65%, #a3d0ef 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#a3d0ef', EndColorStr='#a3d0ef');outline:none}div.DTE button.btn:focus:after,div.DTE div.DTE_Form_Buttons button:focus:after{position:absolute;top:0;left:0;right:0;bottom:0;background:white;display:block;content:" ";-webkit-animation-duration:1s;-webkit-animation-name:buttonPulse;-webkit-animation-fill-mode:forwards;-webkit-animation-iteration-count:infinite;-webkit-animation-timing-function:linear;-webkit-animation-direction:alternate;-moz-animation-duration:1s;-moz-animation-name:buttonPulse;-moz-animation-fill-mode:forwards;-moz-animation-iteration-count:infinite;-moz-animation-timing-function:linear;-moz-animation-direction:alternate;-o-animation-duration:1s;-o-animation-name:buttonPulse;-o-animation-fill-mode:forwards;-o-animation-iteration-count:infinite;-o-animation-timing-function:linear;-o-animation-direction:alternate;animation-duration:1s;animation-name:buttonPulse;animation-fill-mode:forwards;animation-iteration-count:infinite;animation-timing-function:linear;animation-direction:alternate}div.DTE.DTE_Action_Remove div.DTE_Body_Content{text-align:center;padding:20px 0}@-webkit-keyframes buttonPulse{0%{opacity:0}100%{opacity:0.2}}@-moz-keyframes buttonPulse{0%{opacity:0}100%{opacity:0.2}}@-o-keyframes buttonPulse{0%{opacity:0}100%{opacity:0.2}}@keyframes buttonPulse{0%{opacity:0}100%{opacity:0.2}}div.DTTT_container{float:left}div.DTE_Field input,div.DTE_Field textarea{box-sizing:border-box;background-color:white;-webkit-transition:background-color ease-in-out .15s;transition:background-color ease-in-out .15s}div.DTE_Field input:focus,div.DTE_Field textarea:focus{background-color:#ffffee}div.DTE_Field input[type="color"],div.DTE_Field input[type="date"],div.DTE_Field input[type="datetime"],div.DTE_Field input[type="datetime-local"],div.DTE_Field input[type="email"],div.DTE_Field input[type="month"],div.DTE_Field input[type="number"],div.DTE_Field input[type="password"],div.DTE_Field input[type="search"],div.DTE_Field input[type="tel"],div.DTE_Field input[type="text"],div.DTE_Field input[type="time"],div.DTE_Field input[type="url"],div.DTE_Field input[type="week"]{padding:6px 4px;width:100%}div.DTE_Field div.DTE_Field_Info,div.DTE_Field div.DTE_Field_Message{font-size:11px;line-height:1em}div.DTE_Field div.DTE_Field_Error{font-size:11px;line-height:1em;display:none;color:red;margin-top:5px}div.DTE_Field div.multi-value{display:none;border:1px dotted #666;border-radius:3px;padding:5px;background-color:#fafafa;cursor:pointer}div.DTE_Field div.multi-value span{font-size:0.8em;line-height:1.25em;display:block;color:#666}div.DTE_Field div.multi-value:hover{background-color:#f1f1f1}div.DTE_Field div.multi-restore{display:none;margin-top:0.5em;font-size:0.8em;line-height:1.25em;color:#3879d9}div.DTE_Field div.multi-restore:hover{text-decoration:underline;cursor:pointer}div.DTE_Field_Type_textarea textarea{padding:3px;width:100%;height:80px}div.DTE_Field.DTE_Field_Type_date img{vertical-align:middle;cursor:pointer;*cursor:hand}div.DTE_Field.DTE_Field_Type_date input.jqueryui{width:87%;margin-right:6px}div.DTE_Field_Type_checkbox div.DTE_Field_Input>div>div,div.DTE_Field_Type_radio div.DTE_Field_Input>div>div{margin-bottom:0.25em}div.DTE_Field_Type_checkbox div.DTE_Field_Input>div>div:last-child,div.DTE_Field_Type_radio div.DTE_Field_Input>div>div:last-child{margin-bottom:0}div.DTE_Field_Type_checkbox div.DTE_Field_Input>div>div label,div.DTE_Field_Type_radio div.DTE_Field_Input>div>div label{margin-left:0.75em;vertical-align:middle}div.DTE_Field_Type_select div.DTE_Field_Input{padding-top:4px}div.DTE_Body{padding:50px 0}div.DTE_Body div.DTE_Body_Content{position:relative;overflow:auto}div.DTE_Body div.DTE_Body_Content div.DTE_Form_Info{padding:1em 1em 0 1em;margin:0}div.DTE_Body div.DTE_Body_Content div.DTE_Field{position:relative;zoom:1;clear:both;padding:5px 20%;border:1px solid transparent}div.DTE_Body div.DTE_Body_Content div.DTE_Field:after{display:block;content:".";height:0;line-height:0;clear:both;visibility:hidden}div.DTE_Body div.DTE_Body_Content div.DTE_Field:hover{background-color:#f9f9f9;border:1px solid #f3f3f3}div.DTE_Body div.DTE_Body_Content div.DTE_Field>label{float:left;width:40%;padding-top:6px}div.DTE_Body div.DTE_Body_Content div.DTE_Field>div.DTE_Field_Input{float:right;width:60%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full{padding:5px 0 5px 20%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full>label{width:30%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full>div.DTE_Field_Input{width:70%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.block>div.DTE_Field_Input{float:none;clear:both;width:100%}html[dir="rtl"] div.DTE_Body div.DTE_Body_Content div.DTE_Field>label{float:right}html[dir="rtl"] div.DTE_Body div.DTE_Body_Content div.DTE_Field>div.DTE_Field_Input{float:left}html[dir="rtl"] div.DTE div.DTE_Form_Buttons button{float:left}@media only screen and (max-width: 768px){div.DTE_Body div.DTE_Body_Content div.DTE_Field{padding:5px 10%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full{padding:5px 0 5px 10%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full>label{width:35.5%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full>div.DTE_Field_Input{width:64.5%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.block>div.DTE_Field_Input{width:100%}}@media only screen and (max-width: 640px){div.DTE_Body div.DTE_Body_Content div.DTE_Field{padding:5px 0}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full{padding:5px 0%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full>label{width:40%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full>div.DTE_Field_Input{width:60%}div.DTE_Body div.DTE_Body_Content div.DTE_Field.block>div.DTE_Field_Input{width:100%}}@media only screen and (max-width: 580px){div.DTE_Body div.DTE_Body_Content div.DTE_Field{position:relative;zoom:1;clear:both;padding:5px 0}div.DTE_Body div.DTE_Body_Content div.DTE_Field>label{float:none;width:auto;padding-top:0}div.DTE_Body div.DTE_Body_Content div.DTE_Field>div.DTE_Field_Input{float:none;width:auto}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full,div.DTE_Body div.DTE_Body_Content div.DTE_Field.block{padding:5px 0}div.DTE_Body div.DTE_Body_Content div.DTE_Field.full>label,div.DTE_Body div.DTE_Body_Content div.DTE_Field.full>div.DTE_Field_Input,div.DTE_Body div.DTE_Body_Content div.DTE_Field.block>label,div.DTE_Body div.DTE_Body_Content div.DTE_Field.block>div.DTE_Field_Input{width:100%}}div.DTE_Bubble{position:absolute;z-index:11;margin-top:-6px;opacity:0}div.DTE_Bubble div.DTE_Bubble_Liner{position:absolute;bottom:0;border:1px solid black;width:300px;margin-left:-150px;background-color:white;box-shadow:2px 2px 7px #555;border-radius:5px;border:2px solid #444;padding:1em;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Bubble_Table{display:table;width:100%}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Bubble_Table>form{display:table-cell}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Bubble_Table>form div.DTE_Form_Content{padding:0}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Bubble_Table>form div.DTE_Form_Content div.DTE_Field{position:relative;zoom:1;margin-bottom:0.5em}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Bubble_Table>form div.DTE_Form_Content div.DTE_Field:last-child{margin-bottom:0}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Bubble_Table div.DTE_Form_Buttons{display:table-cell;vertical-align:bottom;padding:0 0 0 0.75em;width:1%}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Header{border-top-left-radius:5px;border-top-right-radius:5px}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Header+div.DTE_Form_Info,div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Header+div.DTE_Bubble_Table{padding-top:42px}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Form_Error{float:none;display:none;padding:0;margin-bottom:0.5em}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Bubble_Close{position:absolute;top:-11px;right:-11px;width:22px;height:22px;border:2px solid white;background-color:black;text-align:center;border-radius:15px;cursor:pointer;z-index:12;box-shadow:2px 2px 6px #111}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Bubble_Close:after{content:'\00d7';color:white;font-weight:bold;font-size:18px;line-height:18px}div.DTE_Bubble div.DTE_Bubble_Liner div.DTE_Bubble_Close:hover{background-color:#092079;box-shadow:2px 2px 9px #111}div.DTE_Bubble div.DTE_Bubble_Triangle{position:absolute;height:10px;width:10px;top:-6px;background-color:white;border:2px solid #444;border-top:none;border-right:none;-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}div.DTE_Bubble.DTE_Processing div.DTE_Bubble_Liner:after{position:absolute;content:' ';display:block;top:12px;right:18px;height:12px;width:17px;background:url("../images/ajax-loader-small.gif") no-repeat top left}div.DTE_Bubble.below div.DTE_Bubble_Liner{top:10px;bottom:auto}div.DTE_Bubble.below div.DTE_Bubble_Triangle{top:4px;-webkit-transform:rotate(135deg);-moz-transform:rotate(135deg);-ms-transform:rotate(135deg);-o-transform:rotate(135deg);transform:rotate(135deg)}div.DTE_Bubble_Background{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);background:-ms-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-moz-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-o-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-webkit-gradient(radial, center center, 0, center center, 497, color-stop(0, rgba(0,0,0,0.3)), color-stop(1, rgba(0,0,0,0.7)));background:-webkit-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:radial-gradient(ellipse farthest-corner at center, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);z-index:10}div.DTE_Bubble_Background>div{position:absolute;top:0;right:0;left:0;bottom:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)"}div.DTE_Bubble_Background>div:not([dummy]){filter:progid:DXImageTransform.Microsoft.gradient(enabled='false')}div.DTE_Inline{position:relative;display:table;width:100%}div.DTE_Inline div.DTE_Inline_Field,div.DTE_Inline div.DTE_Inline_Buttons{display:table-cell;vertical-align:middle}div.DTE_Inline div.DTE_Inline_Field div.DTE_Field,div.DTE_Inline div.DTE_Inline_Buttons div.DTE_Field{padding:0}div.DTE_Inline div.DTE_Inline_Field div.DTE_Field>label,div.DTE_Inline div.DTE_Inline_Buttons div.DTE_Field>label{display:none}div.DTE_Inline div.DTE_Inline_Field div.DTE_Form_Buttons button,div.DTE_Inline div.DTE_Inline_Buttons div.DTE_Form_Buttons button{margin:-6px 0 -6px 4px;padding:5px}div.DTE_Inline div.DTE_Field input[type="color"],div.DTE_Inline div.DTE_Field input[type="date"],div.DTE_Inline div.DTE_Field input[type="datetime"],div.DTE_Inline div.DTE_Field input[type="datetime-local"],div.DTE_Inline div.DTE_Field input[type="email"],div.DTE_Inline div.DTE_Field input[type="month"],div.DTE_Inline div.DTE_Field input[type="number"],div.DTE_Inline div.DTE_Field input[type="password"],div.DTE_Inline div.DTE_Field input[type="search"],div.DTE_Inline div.DTE_Field input[type="tel"],div.DTE_Inline div.DTE_Field input[type="text"],div.DTE_Inline div.DTE_Field input[type="time"],div.DTE_Inline div.DTE_Field input[type="url"],div.DTE_Inline div.DTE_Field input[type="week"]{margin:-6px 0}div.DTE_Inline.DTE_Processing:after{position:absolute;content:' ';display:block;top:4px;right:10px;height:12px;width:17px;background:url("../images/ajax-loader-small.gif") no-repeat top left}span.dtr-data div.DTE_Inline{display:inline-table}div.DTED_Lightbox_Wrapper{position:fixed;top:0;left:50%;margin-left:-390px;width:780px;height:100%;z-index:11}div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container{*position:absolute;*top:50%;#position:absolute;#top:50%;display:table;height:100%;width:100%}div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper{*position:relative;#position:relative;display:table-cell;vertical-align:middle;width:100%}div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content{*top:-50%;#top:-50%;position:relative;border:7px solid rgba(220,220,220,0.5);box-shadow:2px 2px 10px #555;border-radius:10px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content div.DTE{background:white;border-radius:6px;box-shadow:0 0 5px #555;border:2px solid #444;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content div.DTE div.DTE_Header{top:2px;left:2px;right:2px;width:auto;border-top-left-radius:5px;border-top-right-radius:5px}div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content div.DTE div.DTE_Footer{bottom:2px;left:2px;right:2px;width:auto;border-bottom-left-radius:5px;border-bottom-right-radius:5px}div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content div.DTED_Lightbox_Close{position:absolute;top:-11px;right:-11px;width:22px;height:22px;border:2px solid white;background-color:black;text-align:center;border-radius:15px;cursor:pointer;z-index:12;box-shadow:2px 2px 6px #111}div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content div.DTED_Lightbox_Close:after{content:'\00d7';color:white;font-weight:bold;font-size:18px;line-height:18px}div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content div.DTED_Lightbox_Close:hover{background-color:#092079;box-shadow:2px 2px 9px #111}div.DTED_Lightbox_Background{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);background:-ms-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-moz-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-o-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:-webkit-gradient(radial, center center, 0, center center, 497, color-stop(0, rgba(0,0,0,0.3)), color-stop(1, rgba(0,0,0,0.7)));background:-webkit-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);background:radial-gradient(ellipse farthest-corner at center, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.7) 100%);z-index:10}div.DTED_Lightbox_Background>div{position:absolute;top:0;right:0;left:0;bottom:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)"}div.DTED_Lightbox_Background>div:not([dummy]){filter:progid:DXImageTransform.Microsoft.gradient(enabled='false')}body.DTED_Lightbox_Mobile div.DTED_Lightbox_Background{height:0}body.DTED_Lightbox_Mobile div.DTED_Lightbox_Shown{display:none}body.DTED_Lightbox_Mobile div.DTED_Lightbox_Wrapper{position:absolute;top:0px;left:0px;right:0px;bottom:0px;width:auto;height:auto;margin-left:0;-webkit-overflow-scrolling:touch}body.DTED_Lightbox_Mobile div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container{display:block}body.DTED_Lightbox_Mobile div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper{display:block}body.DTED_Lightbox_Mobile div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content{border:4px solid rgba(220,220,220,0.5);border-radius:0}body.DTED_Lightbox_Mobile div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content div.DTE{border-radius:0;box-shadow:0 0 5px #555;border:2px solid #444}body.DTED_Lightbox_Mobile div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content div.DTE div.DTE_Header{border-top-left-radius:0;border-top-right-radius:0}body.DTED_Lightbox_Mobile div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content div.DTE div.DTE_Footer{border-bottom-left-radius:0;border-bottom-right-radius:0}body.DTED_Lightbox_Mobile div.DTED_Lightbox_Wrapper div.DTED_Lightbox_Container div.DTED_Lightbox_Content_Wrapper div.DTED_Lightbox_Content div.DTED_Lightbox_Close{top:11px;right:15px}@media only screen and (max-width: 780px){div.DTED_Lightbox_Wrapper{position:fixed;top:0;left:0;width:100%;margin-left:0}}div.DTED_Envelope_Wrapper{position:absolute;top:0;bottom:0;left:50%;height:100%;z-index:11;display:none;overflow:hidden}div.DTED_Envelope_Wrapper div.DTED_Envelope_ShadowLeft{position:absolute;top:0;left:0;width:50%;height:9px;background:url("../images/shadow_left.png") no-repeat top left;z-index:10}div.DTED_Envelope_Wrapper div.DTED_Envelope_ShadowRight{position:absolute;top:0;right:0;width:50%;height:9px;background:url("../images/shadow_right.png") no-repeat top right;z-index:10}div.DTED_Envelope_Wrapper div.DTED_Envelope_Container{position:absolute;top:0;left:5%;width:90%;border-left:1px solid #777;border-right:1px solid #777;border-bottom:1px solid #777;box-shadow:3px 3px 10px #555;border-bottom-left-radius:5px;border-bottom-right-radius:5px;background-color:white}div.DTED_Envelope_Wrapper div.DTED_Envelope_Container div.DTE_Processing_Indicator{right:36px}div.DTED_Envelope_Wrapper div.DTED_Envelope_Container div.DTE_Footer{border-bottom-left-radius:5px;border-bottom-right-radius:5px}div.DTED_Envelope_Wrapper div.DTED_Envelope_Container div.DTED_Envelope_Close{position:absolute;top:16px;right:10px;width:18px;height:18px;cursor:pointer;*cursor:hand;z-index:12;text-align:center;font-size:12px;background:#F8F8F8;background:-webkit-gradient(linear, center bottom, center top, from(#CCC), to(#fff));background:-moz-linear-gradient(top, #fff, #CCC);background:linear-gradient(top, #fff, #CCC);text-shadow:0 1px 0 white;border:1px solid #999;border-radius:2px;-moz-border-radius:2px;-webkit-border-radius:2px;box-shadow:0px 0px 1px #999;-moz-box-shadow:0px 0px 1px #999;-webkit-box-shadow:0px 0px 1px #999}div.DTED_Envelope_Background{position:fixed;top:0;left:0;width:100%;height:100%;z-index:10;background:rgba(0,0,0,0.4);background:-ms-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.4) 100%);background:-moz-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.4) 100%);background:-o-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.4) 100%);background:-webkit-gradient(radial, center center, 0, center center, 497, color-stop(0, rgba(0,0,0,0.1)), color-stop(1, rgba(0,0,0,0.4)));background:-webkit-radial-gradient(center, ellipse farthest-corner, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.4) 100%);background:radial-gradient(ellipse farthest-corner at center, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.4) 100%)}table.dataTable tbody tr.highlight{background-color:#FFFBCC !important}table.dataTable tbody tr.highlight,table.dataTable tbody tr.noHighlight,table.dataTable tbody tr.highlight td,table.dataTable tbody tr.noHighlight td{-webkit-transition:background-color 500ms linear;-moz-transition:background-color 500ms linear;-ms-transition:background-color 500ms linear;-o-transition:background-color 500ms linear;transition:background-color 500ms linear}table.dataTable.stripe tbody tr.odd.highlight,table.dataTable.display tbody tr.odd.highlight{background-color:#f9f5c7}table.dataTable.hover tbody tr:hover.highlight,table.dataTable.hover tbody tr.odd:hover.highlight,table.dataTable.hover tbody tr.even:hover.highlight,table.dataTable.display tbody tr:hover.highlight,table.dataTable.display tbody tr.odd:hover.highlight,table.dataTable.display tbody tr.even:hover.highlight{background-color:#f6f2c5}table.dataTable.order-column tbody tr.highlight>.sorting_1,table.dataTable.order-column tbody tr.highlight>.sorting_2,table.dataTable.order-column tbody tr.highlight>.sorting_3,table.dataTable.display tbody tr.highlight>.sorting_1,table.dataTable.display tbody tr.highlight>.sorting_2,table.dataTable.display tbody tr.highlight>.sorting_3{background-color:#faf6c8}table.dataTable.display tbody tr.odd.highlight>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.highlight>.sorting_1{background-color:#f1edc1}table.dataTable.display tbody tr.odd.highlight>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.highlight>.sorting_2{background-color:#f3efc2}table.dataTable.display tbody tr.odd.highlight>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.highlight>.sorting_3{background-color:#f5f1c4}table.dataTable.display tbody tr.even.highlight>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.highlight>.sorting_1{background-color:#faf6c8}table.dataTable.display tbody tr.even.highlight>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.highlight>.sorting_2{background-color:#fcf8ca}table.dataTable.display tbody tr.even.highlight>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.highlight>.sorting_3{background-color:#fefacb}table.dataTable.display tbody tr:hover.highlight>.sorting_1,table.dataTable.display tbody tr.odd:hover.highlight>.sorting_1,table.dataTable.display tbody tr.even:hover.highlight>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.highlight>.sorting_1,table.dataTable.order-column.hover tbody tr.odd:hover.highlight>.sorting_1,table.dataTable.order-column.hover tbody tr.even:hover.highlight>.sorting_1{background-color:#eae6bb}table.dataTable.display tbody tr:hover.highlight>.sorting_2,table.dataTable.display tbody tr.odd:hover.highlight>.sorting_2,table.dataTable.display tbody tr.even:hover.highlight>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.highlight>.sorting_2,table.dataTable.order-column.hover tbody tr.odd:hover.highlight>.sorting_2,table.dataTable.order-column.hover tbody tr.even:hover.highlight>.sorting_2{background-color:#ece8bd}table.dataTable.display tbody tr:hover.highlight>.sorting_3,table.dataTable.display tbody tr.odd:hover.highlight>.sorting_3,table.dataTable.display tbody tr.even:hover.highlight>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.highlight>.sorting_3,table.dataTable.order-column.hover tbody tr.odd:hover.highlight>.sorting_3,table.dataTable.order-column.hover tbody tr.even:hover.highlight>.sorting_3{background-color:#efebbf}div.DTE div.editor_upload{padding-top:4px}div.DTE div.editor_upload div.eu_table{display:table;width:100%}div.DTE div.editor_upload div.row{display:table-row}div.DTE div.editor_upload div.cell{display:table-cell;position:relative;width:50%;vertical-align:top}div.DTE div.editor_upload div.cell+div.cell{padding-left:10px}div.DTE div.editor_upload div.row+div.row div.cell{padding-top:10px}div.DTE div.editor_upload button.btn,div.DTE div.editor_upload input[type=file]{width:100%;height:2.3em;font-size:0.8em;text-align:center;line-height:1em}div.DTE div.editor_upload input[type=file]{position:absolute;top:0;left:0;width:100%;opacity:0}div.DTE div.editor_upload div.drop{position:relative;box-sizing:border-box;width:100%;height:100%;border:3px dashed #ccc;border-radius:6px;min-height:4em;color:#999;padding-top:3px;text-align:center}div.DTE div.editor_upload div.drop.over{border:3px dashed #111;color:#111}div.DTE div.editor_upload div.drop span{max-width:75%;font-size:0.85em;line-height:1em}div.DTE div.editor_upload div.rendered img{max-width:8em;margin:0 auto}div.DTE div.editor_upload.noDrop div.drop{display:none}div.DTE div.editor_upload.noDrop div.row.second{display:none}div.DTE div.editor_upload.noDrop div.rendered{margin-top:10px}div.DTE div.editor_upload.noClear div.clearValue button{display:none}div.DTE div.editor_upload.multi div.cell{display:block;width:100%}div.DTE div.editor_upload.multi div.cell div.drop{min-height:0;padding-bottom:5px}div.DTE div.editor_upload.multi div.clearValue{display:none}div.DTE div.editor_upload.multi ul{list-style-type:none;margin:0;padding:0}div.DTE div.editor_upload.multi ul li{position:relative;margin-top:0.5em}div.DTE div.editor_upload.multi ul li:first-child{margin-top:0}div.DTE div.editor_upload.multi ul li img{vertical-align:middle}div.DTE div.editor_upload.multi ul li button{position:absolute;width:40px;right:0;top:50%;margin-top:-1.5em}
diff --git a/src/main/webapp/css/jquery_datatables/jquery.dataTables.css b/src/main/webapp/css/jquery_datatables/jquery.dataTables.css
new file mode 100644
index 0000000..55c6d2b
--- /dev/null
+++ b/src/main/webapp/css/jquery_datatables/jquery.dataTables.css
@@ -0,0 +1,485 @@
+/*
+ * Table styles
+ */
+table.dataTable {
+ width: 100%;
+ margin: 0 auto;
+ clear: both;
+ border-collapse: separate;
+ border-spacing: 0;
+ /*
+ * Header and footer styles
+ */
+ /*
+ * Body styles
+ */
+}
+table.dataTable thead th,
+table.dataTable tfoot th {
+ font-weight: bold;
+ /* position: relative; */
+ background-image: none !important;
+}
+table.dataTable thead th,
+table.dataTable thead td {
+ padding: 10px 18px;
+ border-bottom: 1px solid #111;
+}
+table.dataTable thead th:active,
+table.dataTable thead td:active {
+ outline: none;
+}
+table.dataTable tfoot th,
+table.dataTable tfoot td {
+ padding: 10px 18px 6px 18px;
+ border-top: 1px solid #111;
+}
+table.dataTable thead .sorting,
+table.dataTable thead .sorting_asc,
+table.dataTable thead .sorting_desc {
+ cursor: pointer;
+ *cursor: hand;
+}
+table.dataTable thead .sorting:after,
+table.dataTable thead .sorting_asc:after,
+table.dataTable thead .sorting_desc:after {
+ /* position: absolute;
+ top: 12px;
+ right: 8px;
+ display: block; */
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-size: 0.8em;
+}
+table.dataTable thead .sorting,
+table.dataTable thead .sorting_asc,
+table.dataTable thead .sorting_desc,
+table.dataTable thead .sorting_asc_disabled,
+table.dataTable thead .sorting_desc_disabled {
+ background-repeat: no-repeat;
+ background-position: center right;
+}
+table.dataTable thead .sorting:after {
+ content: "\f0dc";
+ color: #0271be;
+ font-size: 0.8em;
+}
+table.dataTable thead .sorting:hover:after,
+table.dataTable thead .sorting_desc:hover:after,
+table.dataTable thead .sorting_asc:hover:after {
+ color: #009AE5;
+}
+table.dataTable thead .sorting_asc:after {
+ content: "\f0de";
+ color: #0271be;
+ font-size: 0.8em;
+}
+table.dataTable thead .sorting_desc:after {
+ content: "\f0dd";
+ color: #0271be;
+}
+table.dataTable thead .sorting_asc_disabled:after {
+
+}
+/* table.dataTable thead .sorting {
+ background-image: url("../../img/sort_both.png");
+}
+table.dataTable thead .sorting_asc {
+ background-image: url("../../img/sort_asc.png");
+}
+table.dataTable thead .sorting_desc {
+ background-image: url("../../img/sort_desc.png");
+}
+table.dataTable thead .sorting_asc_disabled {
+ background-image: url("../../img/sort_asc_disabled.png");
+}
+table.dataTable thead .sorting_desc_disabled {
+ background-image: url("../../img/sort_desc_disabled.png");
+} */
+table.dataTable tbody tr {
+ background-color: #ffffff;
+}
+table.dataTable tbody tr.selected {
+ background-color: #B0BED9;
+}
+table.dataTable tbody th,
+table.dataTable tbody td {
+ padding: 8px 10px;
+}
+table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td {
+ border-top: 1px solid #ddd;
+}
+table.dataTable.row-border tbody tr:first-child th,
+table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th,
+table.dataTable.display tbody tr:first-child td {
+ border-top: none;
+}
+table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td {
+ border-top: 1px solid #ddd;
+ border-right: 1px solid #ddd;
+}
+table.dataTable.cell-border tbody tr th:first-child,
+table.dataTable.cell-border tbody tr td:first-child {
+ border-left: 1px solid #ddd;
+}
+table.dataTable.cell-border tbody tr:first-child th,
+table.dataTable.cell-border tbody tr:first-child td {
+ border-top: none;
+}
+table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd {
+ background-color: #f9f9f9;
+}
+table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected {
+ background-color: #acbad4;
+}
+table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {
+ background-color: #f6f6f6;
+}
+table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected {
+ background-color: #aab7d1;
+}
+table.dataTable.order-column tbody tr > .sorting_1,
+table.dataTable.order-column tbody tr > .sorting_2,
+table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1,
+table.dataTable.display tbody tr > .sorting_2,
+table.dataTable.display tbody tr > .sorting_3 {
+ background-color: #fafafa;
+}
+table.dataTable.order-column tbody tr.selected > .sorting_1,
+table.dataTable.order-column tbody tr.selected > .sorting_2,
+table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1,
+table.dataTable.display tbody tr.selected > .sorting_2,
+table.dataTable.display tbody tr.selected > .sorting_3 {
+ background-color: #acbad5;
+}
+table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 {
+ background-color: #f1f1f1;
+}
+table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 {
+ background-color: #f3f3f3;
+}
+table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 {
+ background-color: whitesmoke;
+}
+table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 {
+ background-color: #a6b4cd;
+}
+table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 {
+ background-color: #a8b5cf;
+}
+table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 {
+ background-color: #a9b7d1;
+}
+table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 {
+ background-color: #fafafa;
+}
+table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 {
+ background-color: #fcfcfc;
+}
+table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 {
+ background-color: #fefefe;
+}
+table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 {
+ background-color: #acbad5;
+}
+table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 {
+ background-color: #aebcd6;
+}
+table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 {
+ background-color: #afbdd8;
+}
+table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {
+ background-color: #eaeaea;
+}
+table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {
+ background-color: #ececec;
+}
+table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {
+ background-color: #efefef;
+}
+table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {
+ background-color: #a2aec7;
+}
+table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {
+ background-color: #a3b0c9;
+}
+table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {
+ background-color: #a5b2cb;
+}
+table.dataTable.no-footer {
+ border-bottom: 1px solid #111;
+}
+table.dataTable.nowrap th, table.dataTable.nowrap td {
+ white-space: nowrap;
+}
+table.dataTable.compact thead th,
+table.dataTable.compact thead td {
+ padding: 4px 17px 4px 4px;
+}
+table.dataTable.compact tfoot th,
+table.dataTable.compact tfoot td {
+ padding: 4px;
+}
+table.dataTable.compact tbody th,
+table.dataTable.compact tbody td {
+ padding: 4px;
+}
+table.dataTable th.dt-left,
+table.dataTable td.dt-left {
+ text-align: left;
+}
+table.dataTable th.dt-center,
+table.dataTable td.dt-center,
+table.dataTable td.dataTables_empty {
+ text-align: center;
+}
+table.dataTable th.dt-right,
+table.dataTable td.dt-right {
+ text-align: right;
+}
+table.dataTable th.dt-justify,
+table.dataTable td.dt-justify {
+ text-align: justify;
+}
+table.dataTable th.dt-nowrap,
+table.dataTable td.dt-nowrap {
+ white-space: nowrap;
+}
+table.dataTable thead th.dt-head-left,
+table.dataTable thead td.dt-head-left,
+table.dataTable tfoot th.dt-head-left,
+table.dataTable tfoot td.dt-head-left {
+ text-align: left;
+}
+table.dataTable thead th.dt-head-center,
+table.dataTable thead td.dt-head-center,
+table.dataTable tfoot th.dt-head-center,
+table.dataTable tfoot td.dt-head-center {
+ text-align: center;
+}
+table.dataTable thead th.dt-head-right,
+table.dataTable thead td.dt-head-right,
+table.dataTable tfoot th.dt-head-right,
+table.dataTable tfoot td.dt-head-right {
+ text-align: right;
+}
+table.dataTable thead th.dt-head-justify,
+table.dataTable thead td.dt-head-justify,
+table.dataTable tfoot th.dt-head-justify,
+table.dataTable tfoot td.dt-head-justify {
+ text-align: justify;
+}
+table.dataTable thead th.dt-head-nowrap,
+table.dataTable thead td.dt-head-nowrap,
+table.dataTable tfoot th.dt-head-nowrap,
+table.dataTable tfoot td.dt-head-nowrap {
+ white-space: nowrap;
+}
+table.dataTable tbody th.dt-body-left,
+table.dataTable tbody td.dt-body-left {
+ text-align: left;
+}
+table.dataTable tbody th.dt-body-center,
+table.dataTable tbody td.dt-body-center {
+ text-align: center;
+}
+table.dataTable tbody th.dt-body-right,
+table.dataTable tbody td.dt-body-right {
+ text-align: right;
+}
+table.dataTable tbody th.dt-body-justify,
+table.dataTable tbody td.dt-body-justify {
+ text-align: justify;
+}
+table.dataTable tbody th.dt-body-nowrap,
+table.dataTable tbody td.dt-body-nowrap {
+ white-space: nowrap;
+}
+
+table.dataTable,
+table.dataTable th,
+table.dataTable td {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+/*
+ * Control feature layout
+ */
+.dataTables_wrapper {
+ position: relative;
+ clear: both;
+ *zoom: 1;
+ zoom: 1;
+}
+.dataTables_wrapper .dataTables_length {
+ float: left;
+}
+.dataTables_wrapper .dataTables_filter {
+ float: right;
+ text-align: right;
+}
+.dataTables_wrapper .dataTables_filter input {
+ margin-left: 0.5em;
+}
+.dataTables_wrapper .dataTables_info {
+ clear: both;
+ float: left;
+ padding-top: 0.755em;
+}
+.dataTables_wrapper .dataTables_paginate {
+ float: right;
+ text-align: right;
+ padding-top: 0.25em;
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button {
+ box-sizing: border-box;
+ display: inline-block;
+ min-width: 1.5em;
+ padding: 0.5em 1em;
+ margin-left: 2px;
+ text-align: center;
+ text-decoration: none !important;
+ cursor: pointer;
+ *cursor: hand;
+ color: #333 !important;
+ border: 1px solid transparent;
+ border-radius: 0px;
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
+ color: #333 !important;
+ border: 1px solid #979797;
+ background-color: white;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));
+ /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%);
+ /* Chrome10+,Safari5.1+ */
+ background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%);
+ /* FF3.6+ */
+ background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%);
+ /* IE10+ */
+ background: -o-linear-gradient(top, white 0%, #dcdcdc 100%);
+ /* Opera 11.10+ */
+ background: linear-gradient(to bottom, white 0%, #dcdcdc 100%);
+ /* W3C */
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
+ cursor: default;
+ color: #666 !important;
+ border: 1px solid transparent;
+ background: transparent;
+ box-shadow: none;
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
+ color: white !important;
+ border: 1px solid #111;
+ background-color: #585858;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));
+ /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #585858 0%, #111 100%);
+ /* Chrome10+,Safari5.1+ */
+ background: -moz-linear-gradient(top, #585858 0%, #111 100%);
+ /* FF3.6+ */
+ background: -ms-linear-gradient(top, #585858 0%, #111 100%);
+ /* IE10+ */
+ background: -o-linear-gradient(top, #585858 0%, #111 100%);
+ /* Opera 11.10+ */
+ background: linear-gradient(to bottom, #585858 0%, #111 100%);
+ /* W3C */
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button:active {
+ outline: none;
+ background-color: #2b2b2b;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));
+ /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
+ /* Chrome10+,Safari5.1+ */
+ background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
+ /* FF3.6+ */
+ background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
+ /* IE10+ */
+ background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
+ /* Opera 11.10+ */
+ background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);
+ /* W3C */
+ box-shadow: inset 0 0 3px #111;
+}
+.dataTables_wrapper .dataTables_paginate .ellipsis {
+ padding: 0 1em;
+}
+.dataTables_wrapper .dataTables_processing {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 100%;
+ height: 40px;
+ margin-left: -50%;
+ margin-top: -25px;
+ padding-top: 20px;
+ text-align: center;
+ font-size: 1.2em;
+ background-color: white;
+ background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));
+ background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
+ background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
+ background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
+ background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
+ background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
+}
+.dataTables_wrapper .dataTables_length,
+.dataTables_wrapper .dataTables_filter,
+.dataTables_wrapper .dataTables_info,
+.dataTables_wrapper .dataTables_processing,
+.dataTables_wrapper .dataTables_paginate {
+ color: #333;
+}
+.dataTables_wrapper .dataTables_scroll {
+ clear: both;
+}
+.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody {
+ *margin-top: -1px;
+ -webkit-overflow-scrolling: touch;
+}
+.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th > div.dataTables_sizing,
+.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td > div.dataTables_sizing {
+ height: 0;
+ overflow: hidden;
+ margin: 0 !important;
+ padding: 0 !important;
+}
+.dataTables_wrapper.no-footer .dataTables_scrollBody {
+ border-bottom: 1px solid #111;
+}
+.dataTables_wrapper.no-footer div.dataTables_scrollHead table,
+.dataTables_wrapper.no-footer div.dataTables_scrollBody table {
+ border-bottom: none;
+}
+.dataTables_wrapper:after {
+ visibility: hidden;
+ display: block;
+ content: "";
+ clear: both;
+ height: 0;
+}
+
+@media screen and (max-width: 767px) {
+ .dataTables_wrapper .dataTables_info,
+ .dataTables_wrapper .dataTables_paginate {
+ float: none;
+ text-align: center;
+ }
+ .dataTables_wrapper .dataTables_paginate {
+ margin-top: 0.5em;
+ }
+}
+@media screen and (max-width: 640px) {
+ .dataTables_wrapper .dataTables_length,
+ .dataTables_wrapper .dataTables_filter {
+ float: none;
+ text-align: center;
+ }
+ .dataTables_wrapper .dataTables_filter {
+ margin-top: 0.5em;
+ }
+}
\ No newline at end of file
diff --git a/src/main/webapp/css/jquery_datatables/select.dataTables.min.css b/src/main/webapp/css/jquery_datatables/select.dataTables.min.css
new file mode 100644
index 0000000..59d808a
--- /dev/null
+++ b/src/main/webapp/css/jquery_datatables/select.dataTables.min.css
@@ -0,0 +1 @@
+table.dataTable tbody>tr.selected,table.dataTable tbody>tr>.selected{background-color:#B0BED9}table.dataTable.stripe tbody>tr.odd.selected,table.dataTable.stripe tbody>tr.odd>.selected,table.dataTable.display tbody>tr.odd.selected,table.dataTable.display tbody>tr.odd>.selected{background-color:#acbad4}table.dataTable.hover tbody>tr.selected:hover,table.dataTable.hover tbody>tr>.selected:hover,table.dataTable.display tbody>tr.selected:hover,table.dataTable.display tbody>tr>.selected:hover{background-color:#aab7d1}table.dataTable.order-column tbody>tr.selected>.sorting_1,table.dataTable.order-column tbody>tr.selected>.sorting_2,table.dataTable.order-column tbody>tr.selected>.sorting_3,table.dataTable.order-column tbody>tr>.selected,table.dataTable.display tbody>tr.selected>.sorting_1,table.dataTable.display tbody>tr.selected>.sorting_2,table.dataTable.display tbody>tr.selected>.sorting_3,table.dataTable.display tbody>tr>.selected{background-color:#acbad5}table.dataTable.display tbody>tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody>tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody>tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody>tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody>tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody>tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody>tr.odd>.selected,table.dataTable.order-column.stripe tbody>tr.odd>.selected{background-color:#a6b4cd}table.dataTable.display tbody>tr.even>.selected,table.dataTable.order-column.stripe tbody>tr.even>.selected{background-color:#acbad5}table.dataTable.display tbody>tr.selected:hover>.sorting_1,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody>tr.selected:hover>.sorting_2,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody>tr.selected:hover>.sorting_3,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_3{background-color:#a5b2cb}table.dataTable.display tbody>tr:hover>.selected,table.dataTable.display tbody>tr>.selected:hover,table.dataTable.order-column.hover tbody>tr:hover>.selected,table.dataTable.order-column.hover tbody>tr>.selected:hover{background-color:#a2aec7}table.dataTable td.select-checkbox{position:relative}table.dataTable td.select-checkbox:before,table.dataTable td.select-checkbox:after{display:block;position:absolute;top:1.2em;left:50%;width:12px;height:12px;box-sizing:border-box}table.dataTable td.select-checkbox:before{content:' ';margin-top:-6px;margin-left:-6px;border:1px solid black;border-radius:3px}table.dataTable tr.selected td.select-checkbox:after{content:'\2714';margin-top:-11px;margin-left:-4px;text-align:center;text-shadow:1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9}div.dataTables_wrapper span.select-info,div.dataTables_wrapper span.select-item{margin-left:0.5em}@media screen and (max-width: 640px){div.dataTables_wrapper span.select-info,div.dataTables_wrapper span.select-item{margin-left:0;display:block}}
diff --git a/src/main/webapp/css/modal.css b/src/main/webapp/css/modal.css
new file mode 100644
index 0000000..d490328
--- /dev/null
+++ b/src/main/webapp/css/modal.css
@@ -0,0 +1,492 @@
+div#usersManagementPortletContainer button.close {
+ color: #d1d1d1 !important;
+ opacity : 1 !important;
+}
+div#usersManagementPortletContainer button.close:hover {
+ color: #0271be !important;
+}
+div#usersManagementPortletContainer span#theHeader {
+ color: #0271be;
+}
+div#usersManagementPortletContainer button#saveUsersRolesModal,
+div#usersManagementPortletContainer #saveUsersRolesModalInAssignRolesModal,
+div#usersManagementPortletContainer #closeUsersRolesModalInAssignRolesModal,
+div#usersManagementPortletContainer #saveUsersTeamsInAssignUsersToGroupsModal,
+div#usersManagementPortletContainer #closeUsersTeamsInAssignUsersToGroupsModal {
+ color : #4cb5d2;
+}
+div#usersManagementPortletContainer button#closeUsersRolesModal,
+div#usersManagementPortletContainer button#acceptUsersRequestsOk,
+div#usersManagementPortletContainer span#acceptRequestHeader,
+div#usersManagementPortletContainer button#acceptDeleteUsersFromCurrentSiteModal,
+div#usersManagementPortletContainer button#closeDeleteUsersFromCurrentSiteModal {
+ color : #4cb5d2;
+}
+div#usersManagementPortletContainer span#deleteCurrentSiteUsersHeader {
+ color : #0271be;
+}
+div#usersManagementPortletContainer div#blueLineBottom,
+ div#usersManagementPortletContainerSiteTeamsEditMode div#blueLineBottom {
+ border-bottom: 2px solid #0271be;
+ padding-bottom: 0.2em;
+}
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-footer,
+div#usersManagementPortletContainer div#usersRequestsModal div.modal-footer {
+ background-color: white;
+ background: white;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput ,
+div#usersManagementPortletContainer span#textAboveTagsInputInAssignRolesModal,
+div#usersManagementPortletContainer span#textAboveTagsInputInAssignUsersToGroupsModal {
+ /* opacity: 0.8; */
+ display : block;
+}
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-body div.bootstrap-tagsinput {
+ diplay : inline-block;
+}
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-body div.bootstrap-tagsinput input{
+ display : none;
+}
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-body label:not(#deleteTheRolesLabel),
+ div#usersManagementPortletContainer div#assignUsersRolesModal div.modal-body label:not(#deleteTheRolesLabelInAssignRolesModal),
+ div#usersManagementPortletContainer div#assignUsersToGroupsModal div.modal-body label:not(#deleteTheRolesLabelInAssignUsersToGroupsModal),
+ #addGroupTeamAttributes label,
+ #groupTeamAttributes label {
+ color: #0271be;
+ display : inline-block;
+ text-transform: capitalize;
+}
+div#usersManagementPortletContainer div#changeUsersTeamsModal div.modal-body label:not(#deleteTheRolesLabel) {
+ color : #4cb5d2;
+ display : inline-block;
+}
+div#usersManagementPortletContainer label#deleteTheRolesLabel {
+ display : inline-block;
+}
+div#usersManagementPortletContainer label#deleteTheRolesLabelInAssignRolesModal,
+div#usersManagementPortletContainer label#deleteTheRolesLabelInAssignUsersToGroupsModal {
+ display : inline-block;
+}
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-body div.bootstrap-tagsinput {
+ display : inline-block;
+ margin-left : 10px;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row,
+div#assignUsersRolesModal span#textAboveTagsInputInAssignRolesModal div.row,
+div#assignUsersToGroupsModal span#textAboveTagsInputInAssignUsersToGroupsModal div.row {
+ margin-left : 0px;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row.usersEmailsTagsInModals div.text-core.span9,
+ div#usersManagementPortletContainer span#textAboveTagsInputInAssignRolesModal div.row.usersEmailsTagsInModals div.text-core.span9,
+ div#usersManagementPortletContainer span#textAboveTagsInputInAssignUsersToGroupsModal div.row.usersEmailsTagsInModals div.text-core.span9 {
+ max-height: 10em !important;
+ overflow-y: scroll !important;
+ margin-top: -0.7em;
+ padding-top: 0.7em;
+}
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-body span#textAboveTagsInput div.row:last-of-type div.bootstrap-tagsinput {
+ border: 1px solid #4cb5d2;
+ border-radius: 8px;
+ margin-top:-7px
+}
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-body span#textAboveTagsInput div.row:nth-last-child(-n+2) div.text-core.span9,
+ div#usersManagementPortletContainer div#assignUsersRolesModal div.modal-body span#textAboveTagsInputInAssignRolesModal div.row:last-of-type div.text-core.span9,
+ div#usersManagementPortletContainer div#assignUsersToGroupsModal div.modal-body span#textAboveTagsInputInAssignUsersToGroupsModal div.row:last-of-type div.text-core.span9 {
+ border-radius: 0.2em;
+ border: 1px solid #d1d1d1;
+ padding: 5px !important;
+}
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-body span#textAboveTagsInput div.row:nth-last-child(-n+2),
+div#usersManagementPortletContainer div#assignUsersRolesModal div.modal-body span#textAboveTagsInputInAssignRolesModal div.row:last-of-type,
+div#usersManagementPortletContainer div#assignUsersToGroupsModal div.modal-body span#textAboveTagsInputInAssignUsersToGroupsModal div.row:last-of-type {
+ margin-top: 1.2em;
+}
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-body span#textAboveTagsInput i {
+ color : #4cb5d2;
+ padding : 12px;
+}
+i.fa.fa-times {
+ padding: 6px !important;
+ color: #d1d1d1 !important;
+}
+i.fa.fa-times:hover {
+ color: #0271be !important;
+}
+div#usersManagementPortletContainer div#dropdownList {
+ max-height:200px;
+ overflow: scroll;
+ margin-left : 10px;
+ border : 1px solid #4cb5d2;
+}
+div#usersManagementPortletContainer div.listElement:not(:last-of-type) {
+ border-bottom: 1px solid #4cb5d2;
+}
+div#usersManagementPortletContainer textarea#roleList {
+ /* margin-left : 10px; */
+ width : 100% !important;
+ border-radius : 7px;
+ padding-top : 8px !important;
+ padding-bottom : 8px !important;
+ display : none !important;
+}
+
+div#usersManagementPortletContainer textarea#roleListInAssignRolesModal {
+ /* margin-left : 10px; */
+ width : 100% !important;
+ border-radius : 7px;
+ padding-top : 8px !important;
+ padding-bottom : 8px !important;
+ display : none !important;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row:last-of-type div.text-core.offset3 {
+ width: 70%;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row:nth-last-child(-n+2) div.text-core.span9 div.text-wrap div.text-arrow,
+ div#usersManagementPortletContainer span#textAboveTagsInputInAssignRolesModal div.row:last-of-type div.text-core.span9 div.text-wrap div.text-arrow,
+ div#usersManagementPortletContainer span#textAboveTagsInputInAssignUsersToGroupsModal div.row:last-of-type div.text-core.span9 div.text-wrap div.text-arrow {
+ -ms-transform: scale(1.6,1.6); /* IE 9 */
+ -webkit-transform: scale(1.6,1.6); /* Safari */
+ transform: scale(1.6,1.6);
+ height: 35px;
+ margin-right : 10px;
+}
+div#usersManagementPortletContainer span#textAboveTagsInput div.row:last-of-type div.text-core.offset3 div.text-wrap {
+ width: 100% !important;
+}
+
+div#usersManagementPortletContainer div.text-dropdown.text-position-below {
+ margin-left: 10px;
+}
+div#usersManagementPortletContainer div.text-tags.text-tags-on-top {
+ margin-left:14px;
+ margin-top:6px;
+}
+div#usersManagementPortletContainer div.text-core.offset3 div.text-wrap div.text-arrow {
+ margin-top: 3%;
+}
+div#usersManagementPortletContainer div#usersRequestsModal {
+ width : 60%;
+ /* top : 33%; */
+ color: #0271be !important;
+}
+@media (min-width: 1700px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left : 35%;
+ }
+}
+@media (min-width: 1550px) and (max-width: 1699px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 37%;
+ }
+}
+@media (min-width: 1350px) and (max-width: 1549px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 40%;
+ }
+}
+@media (min-width: 1300px) and (max-width: 1350px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 41%;
+ }
+}
+@media (min-width: 1250px) and (max-width: 1299px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 42%;
+ }
+}
+@media (min-width: 1100px) and (max-width: 1249px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 44%;
+ }
+}
+@media (min-width: 1050px) and (max-width: 1099px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 46%;
+ }
+}
+@media (min-width: 1000px) and (max-width: 1049px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 47%;
+ }
+}
+@media (min-width: 980px) and (max-width: 999px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 49%;
+ }
+}
+@media (min-width: 900px) and (max-width: 979px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 51%;
+ width: 62%;
+ }
+}
+@media (min-width: 850px) and (max-width: 900px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 52%;
+ }
+}
+@media (min-width: 801px) and (max-width: 849px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 55%;
+ }
+}
+@media (min-width: 768px) and (max-width: 800px){
+ div#usersManagementPortletContainer div#usersRequestsModal {
+ left: 57%;
+ }
+}
+div#usersManagementPortletContainer div#usersRequestsModal .modalSubHeader {
+ margin-top: 1em;
+}
+div#usersManagementPortletContainer button#acceptAll,
+div#usersManagementPortletContainer button#rejectAll,
+div#usersManagementPortletContainer button#sendAcceptance,
+div#usersManagementPortletContainer button#sendRejection {
+ /* color : #4cb5d2 !important */
+}
+div#usersManagementPortletContainer a#reloadUsersRequestsTable {
+ font-size: 14px;
+ background-color: #7EAB10;
+ background: #7EAB10;
+ color: white;
+ display: inline;
+ border-radius: 50% !important;
+ padding: 6px 9px;
+ margin-left: 8px;
+ cursor: pointer;
+}
+div#usersManagementPortletContainer div#usersRequestsTableContainer {
+ position : relative;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceContainer {
+ position : relative;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceContainer.hideSection,
+div#usersManagementPortletContainer div#requestsAcceptanceBody div.row.hideSection {
+ display : none;
+}
+div#usersManagementPortletContainer div#usersRequestsTableContainer.hideSection {
+ display : none;
+}
+div#usersManagementPortletContainer div#clickToGoBack {
+ cursor: pointer;
+ display : inline-block;
+}
+div#usersManagementPortletContainer button.hideButton {
+ display : none !important;
+}
+.emailCBCContainer {
+ margin-bottom: 1em;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceBody > div.row > div.text-core {
+ position : static;
+ height: 100% !important;
+}
+div#requestsAcceptanceBody .recipients label + .text-core,
+.emailCBCContainer .text-core {
+ min-height: 45px !important;
+ overflow-y: scroll;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceBody > div.row:not(last-of-type) {
+ margin-bottom: 1em;
+}
+div#usersManagementPortletContainer textarea#tagsForEmails,
+#CCAdminsEmails, #BCCAdminsEmails {
+ display : none;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceBody label {
+ display : inline-block;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceBody div.row {
+ margin-left : 0px;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceToolbar {
+ display:block;
+ padding: 10px;
+}
+div#usersManagementPortletContainer div#emailForAcceptance,
+ div#usersManagementPortletContainer div#emailForRejection {
+ background-color : rgb(234, 234, 234);
+ background : rgb(234, 234, 234);
+ padding : 10px;
+ border-radius : 7px;
+ color : black;
+}
+div#usersManagementPortletContainer a#reloadUsersRequestsTable.btn.hide{
+ display:none;
+}
+div#usersManagementPortletContainer div.modal-backdrop.fade.in.hideFirstModal {
+ z-index: 1051;
+}
+div#usersManagementPortletContainer div#acceptUsersRequestsModal {
+ z-index: 1052;
+}
+div#usersManagementPortletContainer #editEmailTemplate {
+ float:right;
+ color: #0271be;
+ border-radius: 25%;
+ background-color: #fff;
+ background: #fff;
+ border: none;
+ width: 40px;
+ height: 30px;
+}
+div#usersManagementPortletContainer label#labelRejectionEmail {
+ display:inline-block;
+}
+div#usersManagementPortletContainer input#sendAutomaticRejectionEmail {
+ margin:0px !important;
+}
+div#usersManagementPortletContainer span#theMiniHeader {
+ left: 15px;
+ top: 1.9em;
+ display: inline-block;
+ position: absolute;
+ color : #555;
+}
+div#usersManagementPortletContainer p#deleteTheRoles {
+ margin : 0 0 5px;
+}
+div#usersManagementPortletContainer button#closeUSerDetailsModal,
+div#usersManagementPortletContainer #openEditModal,
+div#usersManagementPortletContainer span#userName,
+div#usersManagementPortletContainer span#detailsFor,
+div#usersManagementPortletContainer span#username, div#userDetailsModal > div.modal-body > div.row-fluid > label {
+ color: #0271be;
+}
+div#usersManagementPortletContainer span#userName {
+ color : #4cb5d2;
+}
+div#usersManagementPortletContainer span#detailsFor{
+ font-size : 17.5px;
+ color : #0271be;
+}
+div#usersManagementPortletContainer span#userName {
+ display:none;
+}
+div#usersManagementPortletContainer div#usersRequestsModal .modal-body {
+ /* overflow-y : visible; */
+ overflow-x : visible;
+}
+div#usersManagementPortletContainer div.modal.fade.in .close:hover,
+ div#usersManagementPortletContainer div.modal.fade.in .btn-link:hover,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.modal.fade.in .btn-link:hover {/* color: #0271be !important; */}
+div#usersManagementPortletContainer span#detailsFor,
+ div#usersManagementPortletContainerSiteTeamsEditMode span#teamPrefixHeader {
+ /* margin-bottom : 8px; */
+}
+div#usersManagementPortletContainer div#deleteUsersFromCurrentSiteModal span#deleteCurrentSiteUsersHeader,
+div#usersManagementPortletContainer div#changeUsersRolesModalHeaderDiv span#theHeader,
+div#usersManagementPortletContainer div#InternalServerErrorModal div#blueLineBottom {
+ font-size : 18px;
+}
+div#usersManagementPortletContainer div#changeUsersRolesModalHeaderDiv {
+ border-bottom: 2px solid #0271be;
+ padding-bottom: 0.2em;
+}
+div#usersManagementPortletContainer div#InternalServerErrorModal div#blueLineBottom {
+ font-size : 18px;
+ padding-bottom : 7px;
+ color: #0271be;
+}
+div#usersManagementPortletContainer div#InternalServerErrorModal #closeInternalServerModal {
+ color : #4cb5d2;
+}
+#usersManagementPortletContainerSiteTeamsEditMode div#editGroupTeamModal div#groupTeamAttributes div.row-fluid {
+ margin-bottom: 10px;
+}
+div#usersManagementPortletContainer textarea#teamsList,
+div#usersManagementPortletContainer textarea#teamsListInAssignUsersToGroupsModal {
+ display : none;
+}
+#usersManagementPortletContainerSiteTeamsEditMode span#teamPrefixHeader,
+ #usersManagementPortletContainerSiteTeamsEditMode span#teamNameHeader,
+ #usersManagementPortletContainerSiteTeamsEditMode span#acceptRequestHeader,
+ #usersManagementPortletContainerSiteTeamsEditMode span#acceptRequestHeader {
+ color: #0271be;
+ font-size : 18px;
+}
+#usersManagementPortletContainerSiteTeamsEditMode .modal-footer,
+ #usersManagementPortletContainerSiteTeamsEditMode .modal-header {
+ border-top : none;
+ border-bottom : none;
+}
+#usersManagementPortletContainerSiteTeamsEditMode .modal.fade button {
+ color: #d1d1d1;
+}
+#usersManagementPortletContainerSiteTeamsEditMode .modal.fade button:hover {
+ color: #0271be;
+}
+#usersManagementPortletContainerSiteTeamsEditMode .modal.fade button.close {
+ opacity : 1;
+}
+#usersManagementPortletContainer #acceptRequestHeader {
+ font-size: 18px;
+}
+div#displayGroupTeamUsersModal {
+ width: 700px;
+ left: 45%;
+}
+div#displayGroupTeamUsersModal .modal-body{
+ padding-top: 0px;
+}
+div.modalHeaderContainer {
+ display: inline-block;
+}
+div.modalSubHeader {
+ /* display: inline-block; */
+ color: #777;
+ font-size: 0.9em;
+}
+#usersRequestsModal .modal-header {
+ padding-bottom: 0px;
+}
+#clickToGoBack {
+ color: #0271be;
+}
+#addGroupTeamAttributes input,
+#addGroupTeamAttributes textarea,
+#groupTeamAttributes input,
+#groupTeamAttributes textarea {
+ border-radius: 0.2em;
+ border: 1px solid #d1d1d1;
+}
+.modal-footer {
+ background: #fff !important;
+ background-color: #fff !important;;
+}
+.modal-footer button {
+ color: #0271be !important;
+ border-radius: 5px !important;
+}
+.modal-footer button:hover {
+ background: #eeeeee !important;
+ background-color:#eeeeee !important;
+}
+.text-arrow .caretContainer {
+ position: absolute;
+ top: 47%;
+ left: 32%;
+}
+span.caretContainer > i.caret {
+ padding: 0 !important;
+ border-top-color: #0271be;
+}
+div.emailSubject {
+ background-color: rgb(234, 234, 234);
+ background: rgb(234, 234, 234);
+ border-radius: 7px
+}
+div.emailSubjectContainer{
+ margin-bottom: 1em;
+}
+div.emailSubject {
+ padding:7px;
+ color: black;
+}
+/* html {
+ overflow:hidden;
+} */
+.grantDenyClass, .denyClass {
+ display:inline;
+}
\ No newline at end of file
diff --git a/src/main/webapp/css/navbar.css b/src/main/webapp/css/navbar.css
new file mode 100644
index 0000000..67f4287
--- /dev/null
+++ b/src/main/webapp/css/navbar.css
@@ -0,0 +1,142 @@
+div#usersManagementPortletContainer ul#myTab {
+ background: #1273c7;
+ background-color: #1273c7;
+ border-radius : 7px;
+}
+div#usersManagementPortletContainer ul#myTab li a{
+ color : white;
+}
+div#usersManagementPortletContainer ul#myTab li a:hover{
+ color : #1273c7;
+ background: white;
+ background-color: white;
+}
+div#usersManagementPortletContainer ul#myTab li.active a{
+ color : white;
+ background: rgb(42, 170, 230);
+ background-color: rgb(42, 170, 230);
+}
+div#usersManagementPortletContainer ul#myTab li.active a{
+ color : white;
+ background: rgb(42, 170, 230);
+ background-color: rgb(42, 170, 230);
+}
+div#usersManagementPortletContainer ul#myTab li.active {
+}
+div#usersManagementPortletContainer div.searchDiv,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.searchDiv {
+ background: #0271be;
+ background-color: #0271be;
+ cursor:pointer;
+ padding: 7px 10px 3px 10px;
+ border-radius:50%;
+ color: white;
+ display:inline-block;
+ float:right;
+ height: 25px;
+}
+div#usersManagementPortletContainer #CurrentUsersTable_filter label,
+div#usersManagementPortletContainer #usersRequestsTable_filter label,
+ div#usersManagementPortletContainerSiteTeamsEditMode #GroupTeamsTable_filter label {
+ display : inline-block;
+}
+div#usersManagementPortletContainer div.dataTables_filter label > input,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_filter label > input {
+ margin-bottom: 0px !important;
+}
+div#usersManagementPortletContainer .hideMe input,
+ div#usersManagementPortletContainerSiteTeamsEditMode .hideMe input,
+ .hideMe input {
+ display:none;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode div#GroupTeamsTableUsers_filter > label {
+ display: inline;
+}
+div#usersManagementPortletContainer ul#myTab {
+ background: #1273c7;
+ background-color: #1273c7;
+ border-radius : 7px;
+}
+div#usersManagementPortletContainer ul#myTab li a{
+ color : white;
+}
+div#usersManagementPortletContainer ul#myTab li a:hover{
+ color : #1273c7;
+ background: white;
+ background-color: white;
+}
+div#usersManagementPortletContainer ul#myTab li.active a{
+ color : white;
+ background: rgb(42, 170, 230);
+ background-color: rgb(42, 170, 230);
+}
+div#usersManagementPortletContainer ul#myTab li.active a{
+ color : white;
+ background: rgb(42, 170, 230);
+ background-color: rgb(42, 170, 230);
+}
+div#usersManagementPortletContainer ul#myTab li.active {
+}
+div#usersManagementPortletContainer div.searchDiv,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.searchDiv,
+ div.searchDiv {
+ background: #0271be;
+ background-color: #0271be;
+ cursor:pointer;
+ padding: 5px 10px 3px 10px;
+ border-radius:50%;
+ color: white;
+ display:inline-block;
+ float:right;
+ height: 25px;
+ position: absolute;
+ right: 0;
+}
+div#usersManagementPortletContainer div.searchDiv:not(.active):hover,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.searchDiv:not(.active):hover {
+ background: #009AE5;
+ background-color: #009AE5;
+ }
+div#usersManagementPortletContainer div.searchDiv.active,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.searchDiv.active,
+ div.searchDiv.active {
+ background: #019ad3;
+ background-color: #019ad3;
+ }
+div#usersManagementPortletContainer #CurrentUsersTable_filter label,
+div#usersManagementPortletContainer #usersRequestsTable_filter label,
+ div#usersManagementPortletContainerSiteTeamsEditMode #GroupTeamsTable_filter label,
+ div#rejectedUsersRequestsManagementTab #rejectedUsersRequestsTable_filter label {
+ display : inline-block;
+ /* position: relative; */
+}
+div#usersManagementPortletContainer div.dataTables_filter label > input,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_filter label > input,
+ #rejectedUsersRequestsTable_filter label > input {
+ margin-bottom: 0px !important;
+ margin-right: 21px;
+}
+
+div#usersManagementPortletContainer .hideMe input,
+ div#usersManagementPortletContainerSiteTeamsEditMode .hideMe input,
+ #rejectedUsersRequestsManagementTab .hideMe input {
+ display:none;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode div#GroupTeamsTableUsers_filter > label {
+ display: inline;
+}
+
+#CurrentUsersTable_filter input, #usersRequestsTable_filter input, #GroupTeamsTable_filter input, #GroupTeamsTableUsers_filter input,
+#rejectedUsersRequestsTable_filter input {
+ border-radius: 1em 0 0 1em;
+}
+
+#CurrentUsersTable_filter input:focus,
+ #usersRequestsTable_filter input:focus,
+ #GroupTeamsTable_filter input:focus,
+ #GroupTeamsTableUsers_filter input:focus {
+ webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(127, 127, 127, 0.6);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(127, 127, 127, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(127, 127, 127, 0.6);
+ border-color: rgba(2, 113, 190, 0.5);
+}
\ No newline at end of file
diff --git a/src/main/webapp/css/pagination.css b/src/main/webapp/css/pagination.css
new file mode 100644
index 0000000..697e8e4
--- /dev/null
+++ b/src/main/webapp/css/pagination.css
@@ -0,0 +1,167 @@
+div#usersManagementPortletContainer div.dataTables_paginate.paging_full_numbers a.paginate_button#CurrentUsersTable_previous,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#CurrentUsersTable_previous,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTable_previous,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTable_previous,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTableUsers_previous,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTableUsers_previous,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#rejectedUsersRequestsTables_previous {
+ background: #0271be;
+ background-color: #0271be;
+ color: white;
+ border:0px;
+ /* border-top-left-radius: 7px; */
+ /* border-bottom-left-radius: 7px; */
+}
+div#usersManagementPortletContainer div.dataTables_paginate.paging_full_numbers a.paginate_button#CurrentUsersTable_previous:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#CurrentUsersTable_previous:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTable_previous:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTable_previous:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTableUsers_previous:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTableUsers_previous:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#rejectedUsersRequestsTables_previous:after{
+ content: "\f04a ";
+}
+div.dataTables_paginate.paging_full_numbers{
+ font-family: FontAwesome;
+}
+div#usersManagementPortletContainer div.dataTables_paginate.paging_full_numbers a.paginate_button#CurrentUsersTable_previous:not(.disabled):hover,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTable_previous:not(.disabled):hover,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTableUsers_previous:not(.disabled):hover,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#rejectedUsersRequestsTables_previous:not(.disabled):hover {
+ color: white;
+ border:0px;
+}
+div#usersManagementPortletContainer div.dataTables_paginate.paging_full_numbers a.paginate_button#CurrentUsersTable_next,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#CurrentUsersTable_next,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTable_next,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTable_next,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTableUsers_next,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTableUsers_next,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#rejectedUsersRequestsTable_next {
+ background: #0271be;
+ background-color: #0271be;
+ color: white !important;
+ border:0px;
+ /* border-top-right-radius: 7px; */
+ /* border-bottom-right-radius: 7px; */
+}
+div#usersManagementPortletContainer div.dataTables_paginate.paging_full_numbers a.paginate_button#CurrentUsersTable_next:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#CurrentUsersTable_next:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTable_next:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTable_next:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTableUsers_next:after,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTableUsers_next:after,
+ div.dataTables_paginate.paging_full_numbers a.paginate_button#rejectedUsersRequestsTables_next:after {
+ content: "\f04e";
+}
+div#usersManagementPortletContainer div.dataTables_paginate.paging_full_numbers a.paginate_button#CurrentUsersTable_next:not(.disabled):hover,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTable_next:not(.disabled):hover,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button#GroupTeamsTableUsers_next:not(.disabled):hover,
+ div.dataTables_paginate.paging_full_numbers a.paginate_button#rejectedUsersRequestsTable_next:not(.disabled):hover {
+ background: rgb(42, 170, 210);
+ background-color: rgb(42, 170, 210);
+ color: white !important;
+ border:0px;
+}
+div#usersManagementPortletContainer div.dataTables_paginate.paging_full_numbers span a.paginate_button.current,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers span a.paginate_button.current,
+ div.dataTables_paginate.paging_full_numbers span a.paginate_button.current {
+ background: rgb(41, 131, 173);
+ background-color: rgb(41, 131, 173);
+ color: white !important;
+ border:0px;
+ cursor : default;
+}
+div#usersManagementPortletContainer div.dataTables_paginate.paging_full_numbers a.paginate_button,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button,
+ div.dataTables_paginate.paging_full_numbers a.paginate_button {
+ color: white !important;
+ background: #0271be;
+ background-color: #0271be;
+ border:0px !important;
+ padding: 2px 15px;
+ margin-top: 6px;
+ font-size: 0.9em;
+}
+div.dataTables_paginate.paging_full_numbers a.first {
+ border-top-left-radius: 7px;
+ border-bottom-left-radius: 7px;
+}
+div.dataTables_paginate.paging_full_numbers a.first:after {
+ content: "\f049";
+}
+div.dataTables_paginate.paging_full_numbers a.previous {
+ /* border-top-left-radius: 7px; */
+ /* border-bottom-left-radius: 7px; */
+}
+div.dataTables_paginate.paging_full_numbers a.previous:after {
+ content: "\f04a";
+}
+div.dataTables_paginate.paging_full_numbers a.next {
+ /* border-top-right-radius: 7px; */
+ /* border-bottom-right-radius: 7px; */
+}
+.paging_full_numbers a.next:after {
+ content: "\f04e";
+}
+div.dataTables_paginate.paging_full_numbers a.last {
+ border-top-right-radius: 7px;
+ border-bottom-right-radius: 7px;
+}
+.paging_full_numbers a.last:after {
+ content: "\f050";
+}
+div.dataTables_paginate.paging_full_numbers a.paginate_button:not(.disabled):hover{
+ border:0px;
+ color: white;
+ background: #4CB5D2 !important;
+ background-color: #4CB5D2 !important;
+}
+div.dataTables_paginate.paging_full_numbers a.paginate_button:not(.disabled):not(.current):active{
+ border:0px;
+ color: white;
+ background: #019ad3 !important;
+ background-color: #019ad3 !important;
+}
+div.dataTables_paginate.paging_full_numbers a.paginate_button.current:hover {
+ border:0px !important;
+ color: white !important;
+ background: rgb(41, 131, 173);
+ background-color: rgb(41, 131, 173) ;
+}
+div#usersManagementPortletContainer .btn-default.moreInfo,
+ div#usersManagementPortletContainerSiteTeamsEditMode .btn-default.moreInfo,
+ .btn-default.moreInfo {
+ border : none;
+ background: rgb(1, 95, 126);
+ background-color: rgb(1, 95, 126);
+ color : white;
+}
+div#usersManagementPortletContainer .btn-default.moreInfo:hover,
+ div#usersManagementPortletContainerSiteTeamsEditMode .btn-default.moreInfo:hover,
+ .btn-default.moreInfo:hover {
+ border : none;
+ background: #327A8A;
+ background-color: #327A8A;
+ color : white;
+}
+div.dataTables_paginate.paging_full_numbers a.paginate_button#usersRequestsTable_previous,
+ div.dataTables_paginate.paging_full_numbers a.paginate_button#usersRequestsTable_previous,
+ div.dataTables_paginate.paging_full_numbers a.paginate_button#rejectedUsersRequestsTable_previous {
+ background: #0271be;
+ background-color: #0271be;
+ /* color: white !important; */
+ border:0px;
+}
+div#usersManagementPortletContainer div.dataTables_paginate.paging_full_numbers a.paginate_button.disabled,
+div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button.disabled,
+div.dataTables_paginate.paging_full_numbers a.paginate_button.disabled {
+ background:#e0e0e0 !important;
+ background-color:#e0e0e0 !important;
+}
+div#usersManagementPortletContainer div.dataTables_paginate.paging_full_numbers a.paginate_button.current,
+div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_paginate.paging_full_numbers a.paginate_button.current,
+div.dataTables_paginate.paging_full_numbers a.paginate_button.current {
+ background: #019AD3;
+ background-color: #019AD3;
+}
\ No newline at end of file
diff --git a/src/main/webapp/css/preloader.css b/src/main/webapp/css/preloader.css
new file mode 100644
index 0000000..3e71422
--- /dev/null
+++ b/src/main/webapp/css/preloader.css
@@ -0,0 +1,23 @@
+div#blanket.shown *{
+ background-color : #fff !important;
+ background : #fff !important;
+ color : #fff !important;
+ pointer-events: none;
+ cursor: default;
+}
+div#blanket.shown .dataTables_info,
+ div#blanket.shown div.dataTables_length select,
+ div#blanket.shown ul.nav.nav-tabs > li > a,
+ div#blanket.shown div.dataTables_paginate.paging_simple_numbers {
+ display : none;
+}
+div#blanket p#preloader.hiddenPreloader,
+ div#blanket p#preloader.hiddenPreloader {
+ display : none;
+}
+div#blanket.shown p#preloader, div#blanket.shown p#preloader {
+ position: fixed;
+ z-index: 1052;
+ top: 30%;
+ left: 45%;
+}
diff --git a/src/main/webapp/css/responsive.bootstrap.css b/src/main/webapp/css/responsive.bootstrap.css
new file mode 100644
index 0000000..109f062
--- /dev/null
+++ b/src/main/webapp/css/responsive.bootstrap.css
@@ -0,0 +1,106 @@
+table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child {
+ position: relative;
+ padding-left: 30px;
+ cursor: pointer;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child:before {
+ top: 8px;
+ left: 4px;
+ height: 16px;
+ width: 16px;
+ display: block;
+ position: absolute;
+ color: white;
+ border: 2px solid white;
+ border-radius: 16px;
+ text-align: center;
+ line-height: 14px;
+ box-shadow: 0 0 3px #444;
+ box-sizing: content-box;
+ content: '+';
+ background-color: #337ab7;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child.dataTables_empty:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child.dataTables_empty:before {
+ display: none;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td:first-child:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th:first-child:before {
+ content: '-';
+ background-color: #d33333;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr.child td:before {
+ display: none;
+}
+table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td:first-child,
+table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th:first-child {
+ padding-left: 27px;
+}
+table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td:first-child:before,
+table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th:first-child:before {
+ top: 5px;
+ left: 4px;
+ height: 14px;
+ width: 14px;
+ border-radius: 14px;
+ line-height: 12px;
+}
+table.dataTable.dtr-column > tbody > tr > td.control,
+table.dataTable.dtr-column > tbody > tr > th.control {
+ position: relative;
+ cursor: pointer;
+}
+table.dataTable.dtr-column > tbody > tr > td.control:before,
+table.dataTable.dtr-column > tbody > tr > th.control:before {
+ top: 50%;
+ left: 50%;
+ height: 16px;
+ width: 16px;
+ margin-top: -10px;
+ margin-left: -10px;
+ display: block;
+ position: absolute;
+ color: white;
+ border: 2px solid white;
+ border-radius: 16px;
+ text-align: center;
+ line-height: 14px;
+ box-shadow: 0 0 3px #444;
+ box-sizing: content-box;
+ content: '+';
+ background-color: #337ab7;
+}
+table.dataTable.dtr-column > tbody > tr.parent td.control:before,
+table.dataTable.dtr-column > tbody > tr.parent th.control:before {
+ content: '-';
+ background-color: #d33333;
+}
+table.dataTable > tbody > tr.child {
+ padding: 0.5em 1em;
+}
+table.dataTable > tbody > tr.child:hover {
+ background: transparent !important;
+}
+table.dataTable > tbody > tr.child ul {
+ display: inline-block;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+table.dataTable > tbody > tr.child ul li {
+ border-bottom: 1px solid #efefef;
+ padding: 0.5em 0;
+}
+table.dataTable > tbody > tr.child ul li:first-child {
+ padding-top: 0;
+}
+table.dataTable > tbody > tr.child ul li:last-child {
+ border-bottom: none;
+}
+table.dataTable > tbody > tr.child span.dtr-title {
+ display: inline-block;
+ min-width: 75px;
+ font-weight: bold;
+}
diff --git a/src/main/webapp/css/selectAllCheckboxes.css b/src/main/webapp/css/selectAllCheckboxes.css
new file mode 100644
index 0000000..2330427
--- /dev/null
+++ b/src/main/webapp/css/selectAllCheckboxes.css
@@ -0,0 +1,68 @@
+div#usersManagementPortletContainer table#CurrentUsersTable thead > tr th:first-of-type,
+ div#usersManagementPortletContainer table#usersRequestsTable thead > tr th:first-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr th:first-of-type {
+ position:relative;
+ }
+ div#usersManagementPortletContainer table#CurrentUsersTable thead > tr th:first-of-type div {
+ position: absolute;
+ bottom: 2px;
+ left: 7px;
+ background: #eeeeee;
+ background-color: #eeeeee;
+ width: 21px;
+ height: 24px;
+ padding: 3px 0px 0px 6px;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr th:first-of-type {
+ text-align: center;
+ cursor: auto;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr th:first-of-type div {
+ position: absolute;
+ bottom: 2px;
+ left: 7px;
+ background: #eeeeee;
+ background-color: #eeeeee;
+ width: 24px;
+ height: 27px;
+ padding-left: 3px;
+}
+div#usersManagementPortletContainer table#usersRequestsTable thead > tr th:first-of-type{
+ padding-left: 2px;
+}
+div#usersManagementPortletContainer table#usersRequestsTable thead > tr th:first-of-type div {
+ position: absolute;
+ bottom: 4px;
+ left: 7px;
+ background: #eeeeee;
+ background-color: #eeeeee;
+ width: 20px;
+ height: 23px;
+ padding: 2px 0px 0px 5px;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable thead > tr th:first-of-type.none div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr th:first-of-type.none div,
+ div#usersManagementPortletContainer table#usersRequestsTable thead > tr th:first-of-type.none div {
+ background-color: rgb(42, 170, 230);
+ background: rgb(42, 170, 230);
+}
+div#usersManagementPortletContainer table#CurrentUsersTable thead > tr th:first-of-type:not(.none):hover div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr th:first-of-type:not(.none):hover div,
+ div#usersManagementPortletContainer table#usersRequestsTable thead > tr th:first-of-type:not(.none):hover div {
+ background-color: rgb(42, 170, 210);
+ background: rgb(42, 170, 210);
+ }
+ div#usersManagementPortletContainer table#CurrentUsersTable thead > tr th:first-of-type:not(.none):hover div i,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr th:first-of-type:not(.none):hover div i,
+ div#usersManagementPortletContainer table#usersRequestsTable thead > tr th:first-of-type:not(.none):hover div i {
+ color: rgb(42, 170, 210);
+ }
+div#usersManagementPortletContainer th i.icon-ok,
+ div#usersManagementPortletContainerSiteTeamsEditMode th i.icon-ok {
+ color: #eeeeee;
+ font-size: 1em;
+}
+div#usersManagementPortletContainer th i.icon-ok.whiteFont,
+ div#usersManagementPortletContainerSiteTeamsEditMode th i.icon-ok.whiteFont {
+ color: #fff;
+}
\ No newline at end of file
diff --git a/src/main/webapp/css/table.css b/src/main/webapp/css/table.css
new file mode 100644
index 0000000..c7c434d
--- /dev/null
+++ b/src/main/webapp/css/table.css
@@ -0,0 +1,581 @@
+div#usersManagementPortletContainer, div#usersManagementPortletContainerSiteTeamsEditMode {
+ background-color : #fff;
+ background : #fff;
+ /* padding: 2px; */
+}
+div#usersManagementPortletContainer i.icon-search,
+ div#usersManagementPortletContainerSiteTeamsEditMode i.icon-search,
+ i.icon-search {
+ -ms-transform: scale(1.2,1.4) !important; /* IE 9 */
+ -webkit-transform: scale(1.2,1.4) !important; /* Safari */
+ transform: sscale(1.2,1.4) !important;
+ display :inline-block !important;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tbody,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tbody,
+ table#rejectedUsersRequestsTable tbody {
+ background-color : rgba(234, 236, 240, 1);
+ background: rgba(234, 236, 240, 1);
+}
+div#usersManagementPortletContainer table#CurrentUsersTable thead > tr,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr ,
+ table#rejectedUsersRequestsTable thead > tr{
+ border-bottom-color : white;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable thead > tr,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr,
+ table#rejectedUsersRequestsTable thead > tr {
+ border: none;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable thead > tr th,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr th,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTableUsers thead > tr th,
+ table#rejectedUsersRequestsTable thead > tr th{
+ border: none;
+ font-weight: bold;
+ padding-bottom: 0px;
+ padding-left: 4px;
+ text-transform: capitalize;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTableUsers thead > tr th:after
+ table#rejectedUsersRequestsTable thead > tr th:after,{
+ font-weight: normal;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable thead > tr th:first-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr th:first-of-type,
+ table#rejectedUsersRequestsTable thead > tr th:first-of-type {
+ width: 14px !important;
+}
+@-moz-document url-prefix() {
+ div#usersManagementPortletContainer table#CurrentUsersTable thead > tr th:first-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr th:first-of-type {
+ width:15px !important;
+ }
+}
+@-moz-document url-prefix() {
+ div#usersManagementPortletContainer table#usersRequestsTable thead > tr th:first-of-type div {
+ left: 7px !important;;
+ }
+}
+/* @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+ div#usersManagementPortletContainer table#CurrentUsersTable thead > tr th:first-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead > tr th:first-of-type {
+ width:17px !important;
+ }
+ div#usersManagementPortletContainer table#usersRequestsTable thead > tr th:first-of-type div {
+ left: 7px !important;;
+ }
+} */
+div#usersManagementPortletContainer table#CurrentUsersTable tbody tr:last-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tbody tr:last-of-type,
+ table#rejectedUsersRequestsTable tbody tr:last-of-type {
+ border: none;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tbody,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tbody,
+ table#rejectedUsersRequestsTable tbody {
+ border: none;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable,
+ table#rejectedUsersRequestsTable {
+ border: none;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tr td:first-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tr td:first-of-type,
+ table#tr:not(.selected):hover td div tr td:first-of-type {
+ :white;
+}
+.dataTable:not(#GroupTeamsTableUsers) tr td {
+ cursor: pointer;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tr.selected td div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tr.selected td div,
+ table#tr:not(.selected):hover td div tr.selected td div {
+ background-color : rgb(42, 170, 230);
+ background : rgb(42, 170, 230);
+ color : #fff;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tr:not(.selected):hover td div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tr:not(.selected):hover td div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTableUsers tr:not(.selected):hover td div,
+ table#rejectedUsersRequestsTable tr:not(.selected):hover td div {
+ background: #4cb5d2;
+ background-color: #4cb5d2;
+ color: white;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTableUsers tr:not(.selected):hover td div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTableUsers tr td div {
+ /* padding-left : 15px !important; */
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tr td:first-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tr td:first-of-type,
+ table#rejectedUsersRequestsTable tr td:first-of-type {
+ padding-top: 1px;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tr:hover td div i.icon-ok,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tr:hover td div i.icon-ok,
+ #rejectedUsersRequestsTable tr:hover td div i.icon-ok {
+ color: #4cb5d2;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tr td div i.icon-ok.whiteFont,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tr td div i.icon-ok.whiteFont,
+ table#rejectedUsersRequestsTable tr td div i.icon-ok.whiteFont {
+ color: white;
+ font-size: 1em;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tbody tr,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tbody tr,
+ table#rejectedUsersRequestsTable tbody tr {
+ background:#fff;
+ padding: 0px;
+ background-color:#fff;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tbody tr td div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tbody tr td div,
+ table#rejectedUsersRequestsTable tbody tr td div {
+ background: #eeeeee;
+ background-color: #eeeeee;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tbody tr td div i,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tbody tr td div i {
+ color: #eeeeee;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tbody tr td,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tbody tr td,
+ table#rejectedUsersRequestsTable tbody tr td {
+ padding: 0px 2px 0px 2px;
+ text-align: center;
+ line-height: 1em;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable thead th,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable thead th,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTableUsers thead th,
+ table#rejectedUsersRequestsTable thead th {
+ text-align: left;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tbody tr div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tbody tr div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTableUsers tbody tr div,
+ div#usersManagementPortletContainer table#usersRequestsTable tbody tr div,
+ table#rejectedUsersRequestsTable tbody tr div {
+ background: #eeeeee;
+ background-color: #eeeeee;
+ padding: 2px 0px 0px 10px;
+ text-align: left;
+ height: 1.5em;
+ line-height : 1.5em;
+ overflow : hidden;
+ -o-text-overflow: ellipsis;
+ -ms-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width:inherit;
+ height: 23px;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tbody tr div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tbody tr div,
+ table#rejectedUsersRequestsTable tbody tr div {
+ background:white;
+ background-color:white;
+ padding:2px;
+ text-align: left;
+ height: 1.5em;
+ line-height : 1.5em;
+ overflow : hidden;
+ -o-text-overflow: ellipsis;
+ -ms-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width:inherit;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable tbody tr td:first-of-type div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tbody tr td:first-of-type div,
+ table#rejectedUsersRequestsTable tbody tr td:first-of-type div {
+ display:inline-block;
+ width: 1.5em;
+ text-align: center;
+ padding: 2px 3px 2px 3px;
+}
+div#usersManagementPortletContainer div.dataTables_info, div.dataTables_length label, table#CurrentUsersTable > thead > tr > th,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_info, div.dataTables_length label, table#GroupTeamsTable > thead > tr > th,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_info, table#GroupTeamsTableUsers > thead > tr > th,
+ table#rejectedUsersRequestsTable > thead > tr> th {
+ color : #0271be; !important
+ font-weight: normal !important;
+ padding-left: 3px;
+ margin-bottom: 0px;
+}
+div.dataTables_length label {
+ font-size: 0.9em;
+
+
+}
+#GroupTeamsTableUsers_length label {
+ padding-top: 6px;
+}
+div#usersManagementPortletContainer table#CurrentUsersTable > tbody > tr > td.dataTables_empty,
+div#usersManagementPortletContainer table#usersRequestsTable > tbody > tr > td.dataTables_empty,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable > tbody > tr > td.dataTables_empty,
+div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable > tbody > tr > td.dataTables_empty,
+table#GroupTeamsTableUsers td.dataTables_empty,
+table#rejectedUsersRequestsTable td.dataTables_empty {
+ background: #d1d1d1;
+ background-color: #d1d1d1;
+ color: #f2f2f2;
+}
+td.dataTables_empty:hover {
+ cursor: default !important;
+}
+/* div#usersManagementPortletContainer div.dataTables_info, div.dataTables_filter,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_info, div.dataTables_filter {
+ margin-bottom: 10px;
+} */
+div#usersManagementPortletContainer div.dataTables_filter,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_filter,
+ #rejectedUsersRequestsManagementTab div.dataTables_filter {
+ margin-top: 1.1em;
+ margin-bottom: 1em;
+ width: 250px;
+ height: 30px;
+ position: relative;
+}
+div#usersManagementPortletContainer div.dataTables_info,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_info,
+ #rejectedUsersRequestsTableContainer div.dataTables_info{
+ padding-left: 10px;
+ font-size: 0.9em;
+ padding-top: 7px;
+ color: #0271be;
+}
+div#displayGroupTeamUsersModal div.dataTables_info {
+ padding-left : 0px;
+}
+div#usersManagementPortletContainer div.dataTables_length,
+div#usersManagementPortletContainer div.dataTables_paginate.paging_simple_numbers,
+ div#usersManagementPortletContainerSiteTeamsEditMode #GroupTeamsTable_wrapper div.dataTables_length,
+div#usersManagementPortletContainerSiteTeamsEditMode #GroupTeamsTable_wrapper div.dataTables_paginate.paging_simple_numbers,
+#rejectedUsersRequestsTable_wrapper div.dataTables_length,
+#rejectedUsersRequestsTable_wrapper div.dataTables_length.paging_simple_numbers {
+ margin-top: 6px;
+}
+.dataTables_length label:hover {
+ cursor: default;
+}
+div#usersManagementPortletContainer div.dataTables_length label > select,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_length label > select,
+ #rejectedUsersRequestsTableContainer div.dataTables_length label > select {
+ width : 65px !important;
+ margin-bottom: 0px;
+ height: 25px;
+}
+@media (min-width: 768px) and (max-width: 979px) {
+ div#usersManagementPortletContainer div.dataTables_length label > select,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_length label > select {
+ font-size: 1.1em;
+ }
+}
+
+div#usersManagementPortletContainer div.dataTables_length label > select:focus,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_length label > select:focus {
+
+ outline: none;
+}
+div#usersManagementPortletContainer table#usersRequestsTable thead > tr th,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable thead > tr th {
+ border: none;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tbody,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tbody {
+ border: none;
+}
+div#usersManagementPortletContainer table#usersRequestsTable,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable,
+ table#rejectedUsersRequestsTable {
+ border: none;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tbody tr:last-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tbody tr:last-of-type,
+ table#rejectedUsersRequestsTable tbody tr:last-of-type {
+ border: none;
+}
+div#usersManagementPortletContainer table#usersRequestsTable thead > tr,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable thead > tr {
+ border-bottom-color : white;
+}
+div#usersManagementPortletContainer table#usersRequestsTable thead > tr,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable thead > tr,
+ table#rejectedUsersRequestsTable thead > tr {
+ border: none;
+}
+div#usersManagementPortletContainer table#usersRequestsTable,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable {
+ text-align: center;
+}
+div#usersManagementPortletContainer table#usersRequestsTable i.icon-ok,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable i.icon-ok,
+ table#rejectedUsersRequestsTable i.icon-ok {
+ /* color : rgb(234, 234, 234); */
+ color : #eeeeee;
+ font-size: 1em;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr:hover td div i.icon-ok.whiteFont,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr:hover td div i.icon-ok.whiteFont,
+ table#rejectedUsersRequestsTable tr:hover td div i.icon-ok.whiteFont {
+ color: white;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr td div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr td div,
+ table#rejectedUsersRequestsTable tr td div {
+ /* background-color : #e7e7e7;
+ background : #e7e7e7; */
+ background-color : #eeeeee;
+ background : #eeeeee;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tbody tr td,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tbody tr td,
+ table#rejectedUsersRequestsTable tbody tr td {
+ padding: 0px 2px 0px 2px;
+ line-height: 1.09em;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tbody tr td div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tbody tr td div,
+ table#rejectedUsersRequestsTable tbody tr td div {
+ padding: 2px 0px 0px 10px;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr td:first-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr td:first-of-type,
+ table#rejectedUsersRequestsTable tr td:first-of-type {
+ cursor:pointer;
+ color:white !important;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr.selected,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr.selected,
+ table#rejectedUsersRequestsTable tr.selected {
+ background-color : white;
+ background : white;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr.selected td div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr.selected td div,
+ table#rejectedUsersRequestsTable tr.selected td div {
+ background-color : rgb(42, 170, 230);
+ background : rgb(42, 170, 230);
+ /* padding: 2px; */
+ color : #fff;
+}
+div#usersManagementPortletContainer table#usersRequestsTable th,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable th,
+ table#rejectedUsersRequestsTable th {
+ text-align : left;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr.selected td:first-of-type div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr.selected td:first-of-type div,
+ table#rejectedUsersRequestsTable tr.selected td:first-of-type div {
+ text-align: center;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr:not(.selected):hover td div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr:not(.selected):hover td div,
+ table#rejectedUsersRequestsTable tr:not(.selected):hover td div {
+ background: rgb(42, 170, 210);
+ background-color: rgb(42, 170, 210);
+ color: white;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr td:first-of-type div,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr td:first-of-type div,
+ table#rejectedUsersRequestsTable tr td:first-of-type div {
+ width: 1.5em;
+ display : inline-block;
+ padding: 2px;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr td:first-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr td:first-of-type,
+ table#rejectedUsersRequestsTable tr td:first-of-type {
+ text-align: center;
+ /* padding-top : 4px; */
+}
+div#usersManagementPortletContainer table#usersRequestsTable thead th:first-of-type,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable thead th:first-of-type,
+ table#rejectedUsersRequestsTable thead th:first-of-type {
+ width : 15px !important;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr:hover td div i.icon-ok,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr:hover td div i.icon-ok,
+ table#rejectedUsersRequestsTable tr:hover td div i.icon-ok {
+ color: rgb(42, 170, 210);
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr:hover,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr:hover,
+ table#rejectedUsersRequestsTable tr:hover {
+ background-color : white;
+ background : white;
+}
+div#usersManagementPortletContainer table#usersRequestsTable tr td:not(:first-of-type),
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable tr td:not(:first-of-type),
+ table#rejectedUsersRequestsTable tr td:not(:first-of-type) {
+ color : black;
+}
+div#usersManagementPortletContainer table#usersRequestsTable > thead > tr > th,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable > thead > tr > th,
+ table#rejectedUsersRequestsTable > thead > tr > th {
+ font-weight: bold;
+ color: #0271be;
+ padding-bottom: 0px;
+ padding-left: 4px;
+ text-transform: capitalize;
+}
+div#usersManagementPortletContainer table#usersRequestsTable > thead > tr > th:after,
+table#rejectedUsersRequestsTable > thead > tr > th:after {
+ font-weight: normal !important;
+}
+div#usersManagementPortletContainer table#usersRequestsTable > thead > tr > th:hover:after,
+ table#GroupTeamsTableUsers > thead > tr > th:hover:after,
+ table#rejectedUsersRequestsTable > thead > tr > th:hover:after {
+ color: #009AE5;
+}
+div#usersManagementPortletContainer table#usersRequestsTable > thead > tr > th:active:after,
+ table#GroupTeamsTableUsers > thead > tr > th:active:after,
+ table#GroupTeamsTable > thead > tr > th:active:after,
+ table#rejectedUsersRequestsTable > thead > tr > th:active:after {
+ color: #019ad3;
+}
+div#usersManagementPortletContainer div.dataTables_filter label,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_filter label,
+ table#rejectedUsersRequestsTable div.dataTables_filter label {
+ color : #0271be !important
+}
+div#usersManagementPortletContainer a#currentUsersTableRefresh,
+ div#usersManagementPortletContainerSiteTeamsEditMode a#currentUsersTableRefresh {
+ font-size: 14px;
+ background-color: #7EAB10;
+ background: #7EAB10;
+ color: white;
+ display: inline;
+ border-radius: 50%;
+ padding: 6px 9px;
+ margin-left: 8px;
+ cursor: pointer;
+
+}
+div#usersManagementPortletContainer div#auxilliaryDiv,
+ div#usersManagementPortletContainerSiteTeamsEditMode div#auxilliaryDiv {
+ padding: 0px 15px;
+}
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-body,
+ div#usersManagementPortletContainerSiteTeamsEditMode div#changeUsersRolesModal div.modal-body,
+ div#assignUsersRolesModal div#changeUsersRolesModal div.modal-body,
+ div#usersManagementPortletContainer div#assignUsersToGroupsModal div.modal-body {
+ display:inline;
+}
+div#usersManagementPortletContainer div#assignUsersRolesModal div.modal-body,
+div#usersManagementPortletContainer div#assignUsersToGroupsModal div.modal-body {
+ overflow-y: visible;
+}
+div#usersManagementPortletContainer div#assignUsersRolesModal div.modal-body{
+ padding-top: 0px;
+}
+div#usersManagementPortletContainer div#assignUsersToGroupsModal div.modal-body,
+div#usersManagementPortletContainer div#changeUsersRolesModal div.modal-body {
+ padding: 0px;
+}
+div#usersManagementPortletContainer div.text-dropdown.text-position-below,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.text-dropdown.text-position-below {
+ top:initial !important;
+}
+div#usersManagementPortletContainer span#rolesArrow,
+ div#usersManagementPortletContainerSiteTeamsEditMode span#rolesArrow,
+ span#rolesArrow {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ /* background: red; */
+ border-style: solid;
+ border-width: 15px 10px 10px 0px;
+ border-color: transparent #fff transparent transparent;
+ position: absolute;
+ bottom : 50%;
+ right: 100%;
+}
+div#usersManagementPortletContainer span#roles,
+ div#usersManagementPortletContainerSiteTeamsEditMode span#roles,
+ span#roles {
+ display : inline-block;
+ position: relative;
+ text-align: left;
+}
+div#usersManagementPortletContainer span#onHoverUser,
+ div#usersManagementPortletContainerSiteTeamsEditMode span#onHoverUser,
+ span#onHoverUser {
+ color : rgb(42, 170, 210);
+ padding-left : 35px;
+ padding-top : 10px;
+}
+div#usersManagementPortletContainer span#rolesContainer,
+ div#usersManagementPortletContainerSiteTeamsEditMode span#rolesContainer,
+ span#rolesContainer {
+ display : inline-block;
+ background-color : #fff;
+ background : #fff;
+ z-index : 3;
+ position : absolute;
+ bottom: 0%;
+ left: 100%;
+ border-radius : 8px;
+ box-shadow: 0px 9px 20px grey;
+ transform: translateY(50%);
+}
+div#usersManagementPortletContainer table#CurrentUsersTable,
+div#usersManagementPortletContainer table#usersRequestsTable,
+ div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable,
+div#usersManagementPortletContainerSiteTeamsEditMode table#usersRequestsTable,
+ table#rejectedUsersRequestsTable {
+ table-layout: fixed;
+}
+div#usersManagementPortletContainer ul#rolesList,
+ div#usersManagementPortletContainerSiteTeamsEditMode ul#rolesList,
+ ul#rolesList {
+ list-style-type: none;
+ margin : 0px;
+ padding : 5px 20px 20px 20px;
+}
+div#usersManagementPortletContainer ul#rolesList li,
+ div#usersManagementPortletContainerSiteTeamsEditMode ul#rolesList li,
+ ul#rolesList li {
+ margin : 0px;
+ padding : 0px 15px;
+ white-space: nowrap;
+}
+div#usersManagementPortletContainer ul#rolesList li:hover,
+ div#usersManagementPortletContainerSiteTeamsEditMode ul#rolesList li:hover,
+ ul#rolesList li:hover {
+ background: #eeeeee;
+ background-color: #eeeeee;
+}
+div#usersManagementPortletContainer td.relative,
+ div#usersManagementPortletContainerSiteTeamsEditMode td.relative,
+ td.relative {
+ position : relative;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTableUsers,
+table#rejectedUsersRequestsTable {
+ border-bottom : none;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTableUsers tbody td,
+table#rejectedUsersRequestsTable tbody td {
+ padding: 3px;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTableUsers tbody td.dataTables_empty,
+table#rejectedUsersRequestsTable tbody td.dataTables_empty {
+ padding: 0px;
+}
+.dataTable td:focus {
+ outline:none !important;
+}
+table:not(#GroupTeamsTableUsers):not(#rejectedUsersRequestsTable) thead th:first-of-type:hover {
+ cursor: pointer;
+}
+table.dataTable {
+ margin-bottom: 2em;
+}
+div#blanket *{
+ outline: none;
+}
\ No newline at end of file
diff --git a/src/main/webapp/css/tabs.css b/src/main/webapp/css/tabs.css
new file mode 100644
index 0000000..4bc92e4
--- /dev/null
+++ b/src/main/webapp/css/tabs.css
@@ -0,0 +1,59 @@
+ul#myTab {
+ margin-bottom : 0px;
+ border-bottom : none;
+ /* background-color : #eeeeee;
+ background : #eeeeee; */
+ background-color : #fff;
+ background : #fff;
+}
+ul#myTab > li.active a.tabTitle {
+ background : #019ad3;
+ background-color : #019ad3;
+ color : #fff;
+ font-size: 1.2em;
+}
+ul#myTab > li.active a.lineBeneathTabTitle {
+ background : #019ad3;
+ background-color : #019ad3;
+ color : #fff;
+}
+ul#myTab > li > a.lineBeneathTabTitle {
+ line-height: 0.8em;
+ font-size:0.8em;
+ padding: 0px 0px 10px 12px;
+ border-radius: 0px;
+}
+ul#myTab > li > a {
+ border: none;
+ padding-bottom: 5px;
+ background : #0271BE;
+ background-color : #0271BE;
+ color: #fff;
+}
+ul#myTab > li.active:hover > a.tabTitle,
+ul#myTab > li.active:hover > a.lineBeneathTabTitle {
+ background : #019ad3;
+ background-color : #019ad3;
+ color : #fff;
+ cursor: default;
+}
+ul#myTab > li > a.tabTitle{
+ font-size: 1.2em;
+}
+ul#myTab > li:hover > a.tabTitle {
+ background: #4CB5D2;
+ background-color: #4CB5D2;
+ color : #fff;
+}
+ul#myTab > li:hover > a.lineBeneathTabTitle {
+ background: #4CB5D2;
+ background-color: #4CB5D2;
+ color : #fff;
+ cursor: pointer;
+}
+#tabsForTables {
+ overflow : inherit;
+}
+span.portlet-title-text.portlet-title-editable {
+ font-size: 0px;;
+}
\ No newline at end of file
diff --git a/src/main/webapp/css/tagsinputForSearchByGroup.css b/src/main/webapp/css/tagsinputForSearchByGroup.css
new file mode 100644
index 0000000..86805ba
--- /dev/null
+++ b/src/main/webapp/css/tagsinputForSearchByGroup.css
@@ -0,0 +1,22 @@
+div.bootstrap-tagsinput span.label-info {
+ background: #0271be;
+ background-color: #0271be;
+}
+div.bootstrap-tagsinput span.label-info span:after{
+ content: 'x';
+ padding: 0 2px;
+ margin-left: 2px;
+}
+div.bootstrap-tagsinput span.label-info span:hover:after{
+ background-color: #019ad3;
+ background: #019ad3;
+}
+#CurrentUsersTable_filter div.bootstrap-tagsinput input {
+ display:none;
+}
+#CurrentUsersTable_filter div.bootstrap-tagsinput {
+ display:inline-block;
+ position:absolute;
+ left: 5%;
+ top: 10%;
+}
\ No newline at end of file
diff --git a/src/main/webapp/css/toolbar.css b/src/main/webapp/css/toolbar.css
new file mode 100644
index 0000000..04a92aa
--- /dev/null
+++ b/src/main/webapp/css/toolbar.css
@@ -0,0 +1,334 @@
+/*-----Portlet topper-----*/
+.portlet-title-text{
+ /* display:none; */
+}
+/*------------------------*/
+div#usersManagementPortletContainer span#numOfSelectedRowsUserReqs,
+ div#usersManagementPortletContainer span#numOfSelectedRows {
+ padding-right: 4px;
+}
+div#usersManagementPortletContainer #usersManagementDiv {
+ font-size: 15px;
+ color : #4cb5d2;
+ display : inline;
+ /* padding-right: 15px; */
+ float: right;
+}
+div#usersManagementPortletContainer div#userRequestsNotifications.notificationsHidden {
+ font-size: 14px;
+ background-color : #d8d8d8;
+ background : #d8d8d8;
+ color: white;
+ display: inline-block;
+ border-radius : 50%;
+ height: 25px;
+ width: 25px;
+ margin-top: 4px;
+ text-align: center;
+ pointer-events: none;
+}
+
+div#usersManagementPortletContainer div#userRequestsNotificationsTabletView.notificationsHidden {
+ font-size: 14px;
+ background-color : #d8d8d8;
+ background : #d8d8d8;
+ color: white;
+ display: inline-block;
+ border-radius : 50%;
+ height: 25px;
+ width: 25px;
+/* margin-top: 4px; */
+ text-align: center;
+ pointer-events: none;
+}
+div#usersManagementPortletContainer div#userRequestsNotifications:after {
+ /* content: 'No pending requests'; */
+ color:#808080;
+ font-size 0.8em;
+}
+div#usersManagementPortletContainer div#userRequestsNotifications.notificationsShown {
+ font-size: 14px;
+ background-color : #c00000;
+ background : #c00000;
+ color: white;
+ display: inline-block;
+ border-radius : 50%;
+ /* padding: 6px 9px; */
+ cursor : pointer;
+ float: right;
+ position: relative;
+ height: 25px;
+ width: 25px;
+ margin-top: 6px;
+ text-align: center;
+}
+#userRequestsNotificationsTabletView {
+ font-size: 14px;
+ background-color : #c00000;
+ background : #c00000;
+ color: white;
+ display: inline-block;
+ border-radius : 50%;
+ cursor : pointer;
+ float: left;
+ position: relative;
+ height: 25px;
+ width: 25px;
+ margin-top: 1em;
+ text-align: center;
+ border: 1px solid #ddd;
+ left: 7px;
+ z-index: 1039;
+}
+div#usersManagementPortletContainer div#toolbarHr,div.toolbarHr {
+ border-bottom: 4px solid #019ad3;
+ display:block !important;
+ padding: 0px !important;
+ margin: 0 !important;
+ height: 0 !important;
+}
+div#usersManagementPortletContainer div#toolbar.hiddenToolbar {
+ display: none;
+}
+div#usersManagementPortletContainer div#toolbar.shownToolbar {
+ background : #eeeeee;
+ background-color : #eeeeee;
+}
+div#usersManagementPortletContainer div#toolbar.shownToolbar > div#displaySelected {
+ cursor : default !important;
+ float : right;
+ /* margin-right: 10px; */
+ color: #808080;
+ /* border-right:1px solid #ddd; */
+}
+div#usersManagementPortletContainer div.toolbarContainer {
+ /* padding: 0px 0px 2px 0px; */
+}
+div#usersManagementPortletContainer div#toolbar div.insideToolbar {
+ padding-right: 15px;
+ padding-left: 15px;
+ padding-top : 10px;
+ color: #0271be;
+ display : inline-block;
+ height : 2em;
+ cursor : pointer;
+}
+div#usersManagementPortletContainer div#toolbar div.insideToolbar:hover {
+ background: #4cb5d2;
+ background-color: #4cb5d2;
+ color : #fff;
+}
+div#usersManagementPortletContainer div#toolbar.shownToolbar:hover div.insideToolbar:last-of-type {
+ background-color : #eeeeee;
+ background : #eeeeee;
+ color : #4cb5d2;
+}
+div#usersManagementPortletContainer div#toolbar.hiddenToolbar div.insideToolbar {
+ background-color : #fff;
+ background : #fff;
+ color : #fff;
+}
+div#usersManagementPortletContainer div#toolbar.hiddenToolbar:hover div.insideToolbar {
+ background-color : #fff;
+ background : #fff;
+ color : #fff;
+ cursor : default;
+}
+div#usersManagementPortletContainer div#usersRequestsTableToolbarContainer div {
+ padding-right: 15px;
+ padding-left: 15px;
+ padding-top : 10px;
+ color: #0271be;
+ display : inline-block;
+ height : 2em;
+ cursor : pointer;
+}
+div#usersManagementPortletContainer div#usersRequestsTableToolbarContainer div:not(#displaySelectedUserReqs):hover {
+ background: #4cb5d2;
+ background-color: #4cb5d2;
+ color : white;
+}
+div#usersManagementPortletContainer div#usersRequestsTableToolbarContainer.hiddenToolbar div:hover {
+ background: white;
+ background-color: white;
+ color : white;
+}
+div#usersManagementPortletContainer div#usersRequestsTableToolbarContainer.shownToolbar > div#displaySelectedUserReqs {
+ cursor : default !important;
+ float : right;
+}
+/* div#usersManagementPortletContainer div#usersRequestsTableToolbarContainer.shownToolbar:hover div:last-of-type {
+ background-color : #eeeeee;
+ background : #eeeeee;
+ color : rgb(13, 99, 200);
+} */
+div#usersManagementPortletContainer div#usersRequestsTableToolbarContainer {
+ background-color : #eeeeee;
+ background : #eeeeee;
+ margin-bottom : 10px;
+}
+div#usersManagementPortletContainer div#usersRequestsTableToolbarContainer.hiddenToolbar,
+ div#usersRequestsTableToolbarContainer.hiddenToolbar div {
+ background-color : white;
+ background : white;
+ color : white !important;
+ cursor : default !important;
+}
+div#usersManagementPortletContainer div#usersRequestsModal div.modal-body {
+ padding-top : 0px !important;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceToolbar i,
+div#usersManagementPortletContainer span:not(.grantDenyClass):not(.denyClass) {
+ display : inline-block;
+}
+#elementBubbletittle{
+ display:inline-block;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceToolbar {
+ background : rgb(234, 234, 234);
+ background-color : rgb(234, 234, 234);
+ display : block;
+}
+div#usersManagementPortletContainer div#requestsAcceptanceToolbar i{
+ transform : scale(1.5,1.5);
+ -webkit-transform : scale(1.5,1.5);
+}
+div#usersManagementPortletContainer span#toolbarText {
+ margin-left: 10px;
+}
+div#usersManagementPortletContainer #currentUsersTableRefresh,
+div#usersManagementPortletContainer #reloadUsersRequestsTable {
+ border : none;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode div#groupTeamsTableToolbarContainer.hiddenToolbar > div:not(#addSiteTeam) {
+ background: #fff;
+ background-color: #fff;
+ color: #fff;
+ cursor : default;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode div#groupTeamsTableToolbarContainer {
+ background: #eeeeee;
+ background-color: #eeeeee;
+}
+
+div#usersManagementPortletContainerSiteTeamsEditMode div#groupTeamsTableToolbarContainer.hiddenToolbar {
+ background: #fff;
+ background-color: #fff;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode div#groupTeamsTableToolbarContainer.hiddenToolbar #deleteSiteTeam,
+div#usersManagementPortletContainerSiteTeamsEditMode div#groupTeamsTableToolbarContainer.hiddenToolbar #editSiteTeam{
+ background: #fff;
+ background-color: #fff;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode div#groupTeamsTableToolbarContainer div {
+ padding-right: 15px;
+ padding-left: 15px;
+ padding-top : 10px;
+ color: #0271be;
+ display : inline-block;
+ height : 2em;
+ cursor : pointer;
+}
+div#usersManagementPortletContainerSiteTeamsEditMode div#groupTeamsTableToolbarContainer div:hover {
+ background: #4cb5d2;
+ background-color: #4cb5d2;
+ color : #fff;
+}
+div.shownToolbar div#usersManagementDiv {
+ float:right;
+}
+
+div#toolbar div#userRequestsNotifications {
+ float:right;
+}
+div#notificationsNumberPlaceHolder {
+ text-align: center;
+ display: inline-block;
+ padding: 3px 0px 0px 0px;
+}
+div#notificationsNumberPlaceHolderTabletView {
+ text-align: center;
+ display: inline-block;
+ padding: 3px 0px 0px 0px;
+}
+div#notificationsNumberPlaceHolder:before {
+ /* content: 'Pending Requests:'; */
+ white-space: nowrap;
+ margin-left: -135px;
+ position: absolute;
+ color:#808080;
+ font-size 0.8em;
+}
+div#notificationsNumberPlaceHolder:hover:before {
+ cursor: default;
+}
+#notificationsTextDiv{
+ float: right;
+ margin-top: 8px;
+ padding-left: 2px;
+ color:#808080;
+ font-size 0.8em;
+}
+#notificationsTextDivTabletView{
+ padding-top: 3px;
+ padding-left: 2px;
+ color:#808080;
+ font-size 0.8em;
+}
+#notificationsTextDivTabletView{
+ float: left;
+ margin-top: 0.9em;
+ padding-left: 13px;
+ color:#808080;
+ font-size 0.8em;
+}
+div#addSiteTeam {
+ display: inline-block;
+ float: left;
+ color: #0271be;
+ height: 27px;
+ padding: 10px 15px 0px 15px;
+ background: #eeeeee;
+ background-color: #eeeeee;
+}
+div#addSiteTeam:hover {
+ color: #eeeeee;
+ background: #4cb5d2;
+ background-color: #4cb5d2;
+ cursor: pointer;
+}
+#newGroupPlusIcon {
+ margin-left: 5px;
+}
+#displaySelectedUserReqs{
+ color:#808080 !important;
+}
+
+@media only screen and (min-width:980px) and (max-width: 1012px) {
+ #userRequestsNotifications{
+ display:none !important;
+ }
+ #notificationsTextDiv{
+ display:none !important;
+ }
+
+ #userRequestsNotificationsTabletView{
+ display:inline-block !important;
+ }
+ #notificationsTextDivTabletView{
+ display:inline-block !important;
+ }
+}
+@media only screen and (min-width:768px) and (max-width: 1012px) {
+
+ div#usersManagementPortletContainer div#toolbar.shownToolbar > div#displaySelected {
+ margin-right: 0px;
+ border-right:none;
+ padding-left: 0px;
+ }
+
+ div#usersManagementPortletContainer div.dataTables_filter label,
+ div#usersManagementPortletContainerSiteTeamsEditMode div.dataTables_filter label {
+ width: 250px;
+ }
+}
\ No newline at end of file
diff --git a/src/main/webapp/icon.png b/src/main/webapp/icon.png
new file mode 100644
index 0000000..807b862
Binary files /dev/null and b/src/main/webapp/icon.png differ
diff --git a/src/main/webapp/img/preloader.gif b/src/main/webapp/img/preloader.gif
new file mode 100644
index 0000000..82fe34f
Binary files /dev/null and b/src/main/webapp/img/preloader.gif differ
diff --git a/src/main/webapp/img/sort_asc.png b/src/main/webapp/img/sort_asc.png
new file mode 100644
index 0000000..a56d0e2
Binary files /dev/null and b/src/main/webapp/img/sort_asc.png differ
diff --git a/src/main/webapp/img/sort_asc_disabled.png b/src/main/webapp/img/sort_asc_disabled.png
new file mode 100644
index 0000000..fb11dfe
Binary files /dev/null and b/src/main/webapp/img/sort_asc_disabled.png differ
diff --git a/src/main/webapp/img/sort_both.png b/src/main/webapp/img/sort_both.png
new file mode 100644
index 0000000..b842539
Binary files /dev/null and b/src/main/webapp/img/sort_both.png differ
diff --git a/src/main/webapp/img/sort_desc.png b/src/main/webapp/img/sort_desc.png
new file mode 100644
index 0000000..90b2951
Binary files /dev/null and b/src/main/webapp/img/sort_desc.png differ
diff --git a/src/main/webapp/img/sort_desc_disabled.png b/src/main/webapp/img/sort_desc_disabled.png
new file mode 100644
index 0000000..c9fdd8a
Binary files /dev/null and b/src/main/webapp/img/sort_desc_disabled.png differ
diff --git a/src/main/webapp/js/DataTableCSSArrowsIssueWorkArround.js b/src/main/webapp/js/DataTableCSSArrowsIssueWorkArround.js
new file mode 100644
index 0000000..8742d2a
--- /dev/null
+++ b/src/main/webapp/js/DataTableCSSArrowsIssueWorkArround.js
@@ -0,0 +1,4 @@
+function removeArrowFromFirstTableColumn(){
+ var tablesSelector = $('#usersManagementPortletContainer #CurrentUsersTable thead th:first, #usersManagementPortletContainer #usersRequestsTable thead th:first, #usersManagementPortletContainerSiteTeamsEditMode #GroupTeamsTable thead th:first, #rejectedUsersRequestsTable thead th:first');
+ tablesSelector.removeClass('sorting_asc');
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/InitializeDataTables.js b/src/main/webapp/js/InitializeDataTables.js
new file mode 100644
index 0000000..052601c
--- /dev/null
+++ b/src/main/webapp/js/InitializeDataTables.js
@@ -0,0 +1,378 @@
+function initializeCurrentUsersTable(){
+ $('table#CurrentUsersTable').on( 'draw.dt', function (e, settings, data) {
+ var info = $('#CurrentUsersTable').DataTable().page.info();
+ CurrentUsersTablePages = info.pages;
+ }).dataTable({
+ data : usersTableData,
+ columns : [
+ {
+ data : "CheckBox"
+ },
+ {
+ data : "UserName",
+ orderable : true
+ },
+ {
+ data : "Email",
+ orderable : true
+ },
+ {
+ data : "FullName",
+ orderable : true
+ },
+ {
+ data : "Roles",
+ orderable : true
+ } ,
+ {
+ data : "Teams",
+ orderable : true
+ } ,
+ {
+ data : "RequestDate",
+ orderable : true,
+ orderData: 8,
+ targets : 6
+ },
+ {
+ data : "ValidationDate",
+ orderable : true,
+ orderData: 9,
+ targets : 7
+ },
+ {
+ data : "RequestDateObject",
+ visible : false,
+ searchable : false,
+ orderable : true,
+ type : "date"
+ },
+ {
+ data : "ValidationDateObject",
+ visible : false,
+ searchable : false,
+ orderable : true,
+ type : "date"
+ }
+ ],
+ pagingType : "full_numbers",
+ language : {
+ "info": "Showing _START_ - _END_ of _TOTAL_ | ",
+ "processing" : "Processing...",
+ "paginate": {
+ "next": "",
+ "previous": "",
+ "first": "",
+ "last": ""
+ },
+ "search": "_INPUT_",
+ "searchPlaceholder": "Search..."
+ },
+ dom: '<"toolbarContainer">frtilp',
+ responsive: {
+ details: {
+ display: $.fn.dataTable.Responsive.display.childRowImmediate,
+ type: ''
+ }
+ },
+ columnDefs: [{"orderable" : false, "targets" : 0},
+ {responsivePriority: 1, targets: 0},
+ {responsivePriority: 2, targets: 1},
+ {responsivePriority: 3, targets: 2},
+ {responsivePriority: 4, targets: 3},
+ {responsivePriority: 5, targets: 5},
+ {responsivePriority: 6, targets: 6},
+ {responsivePriority: 7, targets: 7}]
+ });
+
+ addTagsInputFunctionalityToSearchInput();
+}
+
+function initializeMembershipRequestsTable(){
+ $('table#usersRequestsTable').on( 'draw.dt', function (e, settings, data) {
+ var info = $('#usersRequestsTable').DataTable().page.info();
+ UsersRequestsTablePages = info.pages;
+ }).DataTable({
+ data : reqsTableData,
+ columns : [
+ {
+ data : "CheckBox",
+ orderable : false,
+ },
+ {
+ data : "UserName",
+ orderable : true,
+ },
+ {
+ data : "Email",
+ orderable : true,
+ },
+ {
+ data : "FullName",
+ orderable : true,
+ },
+ {
+ data : "Message",
+ orderable : true,
+ },
+ {
+ data: "RequestDate",
+ orderable : true,
+ orderData : 6,
+ targets : 5
+ },{
+ data : "RequestDateObject",
+ orderable : true,
+ visible : false,
+ searchable : false,
+ type : "date"
+ }
+ ],
+ pagingType : "full_numbers",
+ language : {
+ "info": "Showing _START_ - _END_ of _TOTAL_ | ",
+ "processing" : "Processing...",
+ "paginate": {
+ "next": "",
+ "previous": "",
+ "first": "",
+ "last": ""
+ },
+ "search": "_INPUT_",
+ "searchPlaceholder": "Search..."
+ },
+ dom: '<"usersRequestsTableToolbarContainer">frtilp',
+ responsive: {
+ details: {
+ display: $.fn.dataTable.Responsive.display.childRowImmediate,
+ type: ''
+ }
+ },
+ columnDefs: [{responsivePriority: 1, targets: 0},
+ {responsivePriority: 2, targets: 1},
+ {responsivePriority: 3, targets: 2},
+ {responsivePriority: 4, targets: 3}]
+ });
+}
+
+function initializeRejectedMembershipRequestsTable(){
+ $('table#rejectedUsersRequestsTable')
+ .on('init.dt', function() {
+ constructToolbarForRejectedUsersRequestsTable();
+ })
+ .on( 'draw.dt', function (e, settings, data) {
+// var info = $('#usersRequestsTable').DataTable().page.info();
+// UsersRequestsTablePages = info.pages;
+ }).DataTable({
+ data : reqsTableData,
+ columns : [
+ {
+ data : "CheckBox",
+ orderable : false,
+ },
+ {
+ data : "UserName",
+ orderable : true,
+ },
+ {
+ data : "Email",
+ orderable : true,
+ },
+ {
+ data : "FullName",
+ orderable : true,
+ },
+ {
+ data : "Message",
+ orderable : true,
+ },
+ {
+ data: "RequestDate",
+ orderable : true,
+ orderData : 7,
+ targets : 5
+ },
+ {
+ data: "RejectionDate",
+ orderable : true,
+ orderData : 8,
+ targets : 6
+ },
+ {
+ data: "RequestDateObject",
+ orderable : true,
+ searchable : false,
+ visible : false
+ },
+ {
+ data: "RejectionDateObject",
+ orderable : true,
+ searchable : false,
+ visible : false
+ }
+ ],
+ pagingType : "full_numbers",
+ language : {
+ "info": "Showing _START_ - _END_ of _TOTAL_ | ",
+ "processing" : "Processing...",
+ "paginate": {
+ "next": "",
+ "previous": "",
+ "first": "",
+ "last": ""
+ },
+ "search": "_INPUT_",
+ "searchPlaceholder": "Search..."
+ },
+ dom: '<"rejectedMembershipRequestsTableToolbarContainer">frtilp',
+ responsive: {
+ details: {
+ display: $.fn.dataTable.Responsive.display.childRowImmediate,
+ type: ''
+ }
+ },
+ columnDefs: [{responsivePriority: 1, targets: 0},
+ {responsivePriority: 2, targets: 1},
+ {responsivePriority: 3, targets: 2},
+ {responsivePriority: 4, targets: 3},
+ {responsivePriority: 5, targets: 4}]
+ });
+}
+
+function initializeGroupTeamsTable(){
+ $('table#GroupTeamsTable').on( 'draw.dt', function (e, settings, data) {
+ var info = $('#GroupTeamsTable').DataTable().page.info();
+ GroupTeamsTablePages = info.pages;
+ }).DataTable({
+ data : groupTeamsTableData,
+ columns : [
+ {
+ data : "CheckBox",
+ orderable : false,
+ },
+ {
+ data : "Name",
+ orderable : true,
+ },
+ {
+ data : "Description",
+ orderable : true,
+ },
+ {
+ data : "NumberOfUsers",
+ orderable : true,
+ },
+ {
+ data : "CreationDate",
+ orderable : true,
+ orderData: 7,
+ targets : 4
+ },
+ {
+ data: "LastModificationDate",
+ orderable : true,
+ orderData: 8,
+ targets : 5
+ },
+ {
+ data: "CreatorName",
+ orderable : true
+ },
+ {
+ data : "CreationDateObject",
+ visible : false,
+ searchable : false,
+ orderable : true,
+ type : "date"
+ },
+ {
+ data : "LastModificationDateObject",
+ visible : false,
+ searchable : false,
+ orderable : true,
+ type : "date"
+ }
+ ],
+ language : {
+ "info": "Showing _START_ - _END_ of _TOTAL_ | ",
+ "processing" : "Processing...",
+ "paginate": {
+ "next": "",
+ "previous": "",
+ "first": "",
+ "last": ""
+ },
+ "search": "_INPUT_",
+ "searchPlaceholder": "Search..."
+ },
+ dom: '<"groupTeamsTableToolbarContainer">frtilp',
+ pagingType : "full_numbers",
+ responsive: {
+ details: {
+ display: $.fn.dataTable.Responsive.display.childRowImmediate,
+ type: ''
+ }
+ },
+ columnDefs: [{responsivePriority: 1, targets: 0},
+ {responsivePriority: 2, targets: 1},
+ {responsivePriority: 3, targets: 2},
+ {responsivePriority: 4, targets: 3}]
+ });
+}
+
+function initializeSiteTeamUsersTable(){
+ $('table#GroupTeamsTableUsers').on( 'draw.dt', function (e, settings, data) {
+ var info = $('#GroupTeamsTableUsers').DataTable().page.info();
+ GroupTeamsTableUsersTablePages = info.pages;
+ if(GroupTeamsTableUsersTablePages <= 1){
+ $('#GroupTeamsTableUsers_paginate').addClass('hidden');
+ }else{
+ $('#GroupTeamsTableUsers_paginate').removeClass('hidden');
+ }
+ }).dataTable({
+ data : [],
+ columns : [
+ {
+ data : "FullName",
+ orderable : true
+ },
+ {
+ data : "UserName",
+ orderable : true
+ }
+ ],
+ pagingType : "full_numbers",
+ dom: 'frtilp',
+ language : {
+ "info": "Showing _START_ - _END_ of _TOTAL_ | ",
+ "processing" : "Processing...",
+ "paginate": {
+ "next": "",
+ "previous": "",
+ "first": "",
+ "last": ""
+ },
+ "search": "_INPUT_",
+ "searchPlaceholder": "Search..."
+ }
+ });
+}
+
+function setTooltips(){
+// $('#userRequestsNotifications').tooltip();
+ $('#currentUsersTableRefresh').tooltip();
+ $('#reloadUsersRequestsTable').tooltip();
+ $('#editEmailTemplate').tooltip();
+// $('#usersManagementPortletContainer .searchDiv').tooltip();
+// $('#usersManagementPortletContainerSiteTeamsEditMode .searchDiv').tooltip();
+}
+
+function eraseTextOfSearchInputLabels(){
+ var array = $('#CurrentUsersTable_filter label, #usersRequestsTable_filter label, #GroupTeamsTable_filter label, #GroupTeamsTableUsers_filter label');
+
+ $.each(array, function(){
+ $($(this).contents()[0]).remove();
+ });//remove Search: text from label
+
+ array.find('input').attr('placeholder', 'Search:');
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/TagFunctionalities.js b/src/main/webapp/js/TagFunctionalities.js
new file mode 100644
index 0000000..609710f
--- /dev/null
+++ b/src/main/webapp/js/TagFunctionalities.js
@@ -0,0 +1,336 @@
+function tagEvents(theList, teamsList){
+ //Roles
+ $('#roleList').textext({
+ plugins : 'autocomplete arrow tags',
+ html : {
+ arrow:'
'
+ },
+ ext : {
+ tags : {
+ addTags : function(tags) {
+ if(!alreadyExists(tags)) {
+ $.fn.textext.TextExtTags.prototype.addTags.apply(this, arguments);
+ }
+ }
+ }
+ }
+ })
+ .bind('getSuggestions', function(e, data)
+ {
+ var list = theList,
+ textext = $(e.target).textext()[0],
+ query = (data ? data.query : '') || ''
+ ;
+
+ $(this).trigger(
+ 'setSuggestions',
+ {
+ result : textext.itemManager().filter(list, query)
+ }
+ );
+ });
+
+ $('#roleListInAssignRolesModal').textext({
+ plugins : 'autocomplete arrow tags',
+ html : {
+ arrow:'
'
+ },
+ ext : {
+ tags : {
+ addTags : function(tags) {
+ if(!alreadyExistsInAssignModal(tags)) {
+ $.fn.textext.TextExtTags.prototype.addTags.apply(this, arguments);
+ }
+ }
+ }
+ }
+ })
+ .bind('getSuggestions', function(e, data)
+ {
+ var list = theList,
+ textext = $(e.target).textext()[0],
+ query = (data ? data.query : '') || ''
+ ;
+
+ $(this).trigger(
+ 'setSuggestions',
+ {
+ result : textext.itemManager().filter(list, query)
+ }
+ );
+ });
+ //UI
+ $('#roleListInAssignRolesModal').closest('.text-core').addClass('span9')
+
+ //Teams
+ $('#teamsList').textext({
+ plugins : 'autocomplete arrow tags',
+ html : {
+ arrow:'
'
+ },
+ ext : {
+ tags : {
+ addTags : function(tags) {
+ if(!alreadyExists(tags)) {
+ $.fn.textext.TextExtTags.prototype.addTags.apply(this, arguments);
+ }
+ }
+ }
+ }
+ })
+ .bind('getSuggestions', function(e, data)
+ {
+ var list = teamsList,
+ textext = $(e.target).textext()[0],
+ query = (data ? data.query : '') || ''
+ ;
+
+ $(this).trigger(
+ 'setSuggestions',
+ {
+ result : textext.itemManager().filter(list, query)
+ }
+ );
+ });
+
+ $('#teamsListInAssignUsersToGroupsModal').textext({
+ plugins : 'autocomplete arrow tags',
+ html : {
+ arrow:'
'
+ },
+ ext : {
+ tags : {
+ addTags : function(tags) {
+ if(!alreadyExistsInAssignUsersToGroupsModal(tags)) {
+ $.fn.textext.TextExtTags.prototype.addTags.apply(this, arguments);
+ }
+ }
+ }
+ }
+ })
+ .bind('getSuggestions', function(e, data)
+ {
+ var list = teamsList,
+ textext = $(e.target).textext()[0],
+ query = (data ? data.query : '') || ''
+ ;
+
+ $(this).trigger(
+ 'setSuggestions',
+ {
+ result : textext.itemManager().filter(list, query)
+ }
+ );
+ });
+ //UI
+ $('#teamsListInAssignUsersToGroupsModal').closest('.text-core').addClass('span9')
+
+ //Emails
+ $('#tagsForEmails').textext({
+ plugins : 'tags'
+ });
+ $('#tagsForEmails').closest('.row').find('.text-core').addClass('span11');
+ $('#usersManagementPortletContainer div.text-tags').off().bind('DOMNodeInserted', function(event) {
+ var element = event.target;
+ var tagName = $(element).prop("tagName");
+ if(tagName !== 'DIV')return;
+ $(this).find('.text-button').addClass('span12');
+ $(this).find('.text-label').addClass('span11');
+ $('#tagsForEmails').parent().find('a.text-remove').html(' ').removeClass('text-remove').addClass('tag-remove span1');
+ });
+ $('span#textAboveTagsInput div.row div.text-core:first-of-type').addClass('span9');
+
+ $('#CCAdminsEmails').textext({
+ plugins : 'tags'
+ });
+
+ $('#BCCAdminsEmails').textext({
+ plugins : 'tags',
+ html: {
+ tag : ''
+ }
+ });
+ $('textarea#BCCAdminsEmails').textext()[0].tags().containerElement().closest('.text-core').addClass('span11');
+}
+
+function alreadyExists(tags){
+ var roleTexts = $('#roleList').parent().find('.text-button.span12 .text-label');
+ var teamTexts = $('#teamsList').parent().find('.text-button.span12 .text-label');
+ var elements = $.merge(roleTexts, teamTexts);
+
+ for(var i = 0; i < elements.length; i++){
+ if(tags === null) return false;
+ for(var j = 0; j < tags.length; j++){
+ if($(elements[i]).text().trim() === tags[j].trim()){
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function alreadyExistsInAssignModal(tags){
+ var roleTextsInAssignModal = $('#roleListInAssignRolesModal').parent().find('.text-button.span12 .text-label');
+ var elements = roleTextsInAssignModal;
+
+ for(var i = 0; i < elements.length; i++){
+ if(tags === null) return false;
+ for(var j = 0; j < tags.length; j++){
+ if($(elements[i]).text().trim() === tags[j].trim()){
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function alreadyExistsInAssignUsersToGroupsModal(tags){
+ var teamTexts = $('#teamsListInAssignUsersToGroupsModal').parent().find('.text-button.span12 .text-label');
+ var elements = teamTexts;
+
+ for(var i = 0; i < elements.length; i++){
+ if(tags === null) return false;
+ for(var j = 0; j < tags.length; j++){
+ if($(elements[i]).text().trim() === tags[j].trim()){
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function teamEditedOrDeleted(teamsList){
+ $('#teamsList').closest('.text-core.span9').remove();
+ $('#textAboveTagsInput .row:nth-of-type(5)').append(
+ $('', {
+ id : 'teamsList'
+ })
+ );
+ $('#teamsList').textext({
+ plugins : 'autocomplete arrow tags',
+ html : {
+ arrow:'
'
+ },
+ ext : {
+ tags : {
+ addTags : function(tags) {
+ if(!alreadyExists(tags)) {
+ $.fn.textext.TextExtTags.prototype.addTags.apply(this, arguments);
+ }
+ }
+ }
+ }
+ })
+ .bind('getSuggestions', function(e, data)
+ {
+ var list = teamsList,
+ textext = $(e.target).textext()[0],
+ query = (data ? data.query : '') || ''
+ ;
+
+ $(this).trigger(
+ 'setSuggestions',
+ {
+ result : textext.itemManager().filter(list, query)
+ }
+ );
+ });
+
+ $('span#textAboveTagsInput div.row:nth-of-type(5) div.text-core:first').addClass('span9');
+
+ $('#teamsList').parent().find('div.text-tags').off().bind(
+ 'DOMNodeInserted',
+ function(event) {
+ var element = event.target;
+ var tagName = $(element).prop("tagName");
+ if (tagName !== 'DIV')
+ return;
+ // $('#roleList').parent().find('div.text-tag').addClass('span5');
+ $('#teamsList').parent().find('div.text-button').addClass(
+ 'span12');
+ $('#teamsList').parent().find('a.text-remove').html(' ')
+ .removeClass('text-remove').addClass('tag-remove');
+ $('textarea#teamsList').parent().find('a.tag-remove').off().on(
+ 'click', function() {
+ $(this).closest('.text-tag').remove();
+ });
+ var matched = false;
+ var tagsTextt = $('#teamsList').parent().find(
+ 'div.text-tag.span5');
+ for (var i = 0; i < tagsTextt.length; i++) {
+ for (var j = i + 1; j < tagsTextt.length; j++) {
+ if ($(tagsTextt[i]).text() === $(tagsTextt[j]).text()) {
+ tagsTextt[j].remove();
+ }
+ }
+ }
+ });
+
+
+ $('#teamsListInAssignUsersToGroupsModal').closest('.text-core.span9').remove();
+ $('#textAboveTagsInputInAssignUsersToGroupsModal .row:last').append(
+ $('', {
+ id : 'teamsListInAssignUsersToGroupsModal'
+ })
+ );
+
+ $('#teamsListInAssignUsersToGroupsModal').textext({
+ plugins : 'autocomplete arrow tags',
+ html : {
+ arrow:'
'
+ },
+ ext : {
+ tags : {
+ addTags : function(tags) {
+ if(!alreadyExistsInAssignUsersToGroupsModal(tags)) {
+ $.fn.textext.TextExtTags.prototype.addTags.apply(this, arguments);
+ }
+ }
+ }
+ }
+ })
+ .bind('getSuggestions', function(e, data)
+ {
+ var list = teamsList,
+ textext = $(e.target).textext()[0],
+ query = (data ? data.query : '') || ''
+ ;
+
+ $(this).trigger(
+ 'setSuggestions',
+ {
+ result : textext.itemManager().filter(list, query)
+ }
+ );
+ });
+
+ $('span#textAboveTagsInputInAssignUsersToGroupsModal div.row:last div.text-core:first').addClass('span9');
+
+ $('#teamsListInAssignUsersToGroupsModal').parent().find('div.text-tags').off().bind(
+ 'DOMNodeInserted',
+ function(event) {
+ var element = event.target;
+ var tagName = $(element).prop("tagName");
+ if (tagName !== 'DIV')
+ return;
+ // $('#roleList').parent().find('div.text-tag').addClass('span5');
+ $('#teamsListInAssignUsersToGroupsModal').parent().find('div.text-button').addClass(
+ 'span12');
+ $('#teamsListInAssignUsersToGroupsModal').parent().find('a.text-remove').html(' ')
+ .removeClass('text-remove').addClass('tag-remove');
+ $('textarea#teamsListInAssignUsersToGroupsModal').parent().find('a.tag-remove').off().on(
+ 'click', function() {
+ $(this).closest('.text-tag').remove();
+ });
+ var matched = false;
+ var tagsTextt = $('#teamsListInAssignUsersToGroupsModal').parent().find(
+ 'div.text-tag.span5');
+ for (var i = 0; i < tagsTextt.length; i++) {
+ for (var j = i + 1; j < tagsTextt.length; j++) {
+ if ($(tagsTextt[i]).text() === $(tagsTextt[j]).text()) {
+ tagsTextt[j].remove();
+ }
+ }
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/Toolbar.js b/src/main/webapp/js/Toolbar.js
new file mode 100644
index 0000000..3cca78f
--- /dev/null
+++ b/src/main/webapp/js/Toolbar.js
@@ -0,0 +1,256 @@
+function constructToolbarForCurrentUsersTable(){
+ var toolbar = $('
', {
+ id: 'toolbar',
+ class: 'shownToolbar',
+ css: {
+ display: 'none'
+ }
+// 'class' : 'hiddenToolbar'
+ });
+ var dispSelected = $('
', {
+ id : 'displaySelected',
+ class: 'insideToolbar'
+ });
+ dispSelected.append($(' ', {
+ id : 'numOfSelectedRows'
+ }));
+ dispSelected.append($(' ', {
+ id : 'justSelectedText',
+ text : ' selected'
+ }));
+
+ var notificationsWrapper = $('
', {
+ id : 'userRequestsNotifications',
+ 'class' : 'notificationsShown hidden',
+// 'class' : 'notificationsShown visible-desktop',
+ 'data-toggle' : "tooltip",
+ 'data-placement':"left",
+ 'data-original-title':"Users' membership requests",
+ css : {
+ display: 'none'
+ }//hide element
+ });
+
+ var notificationsNumberPlaceHolder = $('
', {
+ id: 'notificationsNumberPlaceHolder',
+ text: 0
+ });
+
+ var notificationsTextSpan = $(' ', {
+ id : 'notificationsTextDiv',
+ text: ' pending requests',
+ class : 'hidden',
+ css : {
+ display: 'none'
+ }//hide element
+// class : 'visible-desktop'
+ });
+
+ notificationsWrapper.append(notificationsNumberPlaceHolder);
+
+
+
+ var notificationsWrapperTabletView = $('
', {
+ id : 'userRequestsNotificationsTabletView',
+// 'class' : 'notificationsShown visible-tablet',
+ 'class' : 'notificationsShown',
+ 'data-toggle' : "tooltip",
+ 'data-placement':"left",
+ 'data-original-title':"Users' membership requests"
+ });
+
+ var notificationsNumberPlaceHolderTabletView = $('
', {
+ id: 'notificationsNumberPlaceHolderTabletView',
+ text: 0
+ });
+
+ var notificationsTextSpanTabletView = $(' ', {
+ id : 'notificationsTextDivTabletView',
+ text: ' pending requests',
+ class : ''
+// class : 'visible-tablet'
+ });
+
+ toolbar
+ .append($('
', {
+ id : 'deselectAll',
+ class: 'insideToolbar',
+ text : 'Deselect All'
+ })).append($('
', {
+ id : 'editSelected',
+ class: 'insideToolbar',
+ text : 'Edit Selected'
+ })).append($('
', {
+ id : 'assignRolesToUser',
+ class: 'insideToolbar',
+ text : 'Assign Roles'
+ })).append($('
', {
+ id : 'assignUsersToGroup',
+ class: 'insideToolbar',
+ text : 'Add to Group'
+ })).append($('
', {
+ id : 'deleteSelected',
+ text : 'Unsubscribe Selected',
+ class: 'insideToolbar'
+ })).append(notificationsTextSpan).append(notificationsWrapper).append(dispSelected);
+
+// .append($('
', {
+// id : 'usersManagementDiv',
+// text : 'Pending Requests:'
+// }))//.append(currentUsersTableRefresh)
+
+ $("#usersManagementPortletContainer div.toolbarContainer")
+ .append($('
', {
+ id : 'toolbarHr'
+ })).append(toolbar);
+
+ notificationsWrapperTabletView.append(notificationsNumberPlaceHolderTabletView);
+ notificationsTextSpanTabletView.insertAfter(toolbar);
+ notificationsWrapperTabletView.insertAfter(toolbar);
+}
+
+function searchInputFixForCurrentUsersTable(){
+ var a = $('
', {
+ 'class': 'searchDiv',
+ 'data-toggle' : "tooltip",
+ 'data-placement': "top",
+ 'data-original-title':"Search"
+ }).append($(' ', {
+ 'class' : "icon-search"
+ }).prop('outerHTML'));
+ $('#CurrentUsersTable_filter').append(a);
+ $('#CurrentUsersTable_filter label').toggleClass('hideMe');
+
+ $('div#changeUsersRolesModal div.modal-body span#textAboveTagsInput div.bootstrap-tagsinput').addClass('span9');
+}
+
+function searchInputFixForMembershipRequestsTable(){
+ var aa = $('
', {
+ 'class': 'searchDiv',
+ 'data-toggle' : "tooltip",
+ 'data-placement': "top",
+ 'data-original-title':"Search"
+ }).append($(' ', {
+ 'class' : "icon-search"
+ }).prop('outerHTML'));
+ $('#usersRequestsTable_filter').append(aa);
+ $('#usersRequestsTable_filter label').toggleClass('hideMe');
+}
+
+function searchInputFixForSiteTeamsEditTable(){
+ var aa = $('
', {
+ 'class': 'searchDiv',
+ 'data-toggle' : "tooltip",
+ 'data-placement': "top",
+ 'data-original-title':"Search"
+ }).append($(' ', {
+ 'class' : "icon-search"
+ }).prop('outerHTML'));
+ $('#GroupTeamsTable_filter').append(aa);
+ $('#GroupTeamsTable_filter label').toggleClass('hideMe');
+}
+
+function searchInputFixForSiteTeamsUsersTable(){
+ var aa = $('
', {
+ 'class': 'searchDiv',
+ 'data-toggle' : "tooltip",
+ 'data-placement': "top",
+ 'data-original-title':"Search"
+ }).append($(' ', {
+ 'class' : "icon-search"
+ }).prop('outerHTML'));
+ $('#GroupTeamsTableUsers_filter').append(aa);
+ $('#GroupTeamsTableUsers_filter label').toggleClass('hideMe');
+}
+
+function searchInputFixForRejectedUsersRequestsTable(){
+ var aa = $('
', {
+ 'class': 'searchDiv',
+ 'data-toggle' : "tooltip",
+ 'data-placement': "top",
+ 'data-original-title':"Search"
+ }).append($(' ', {
+ 'class' : "icon-search"
+ }).prop('outerHTML'));
+ $('#rejectedUsersRequestsTable_filter').append(aa);
+ $('#rejectedUsersRequestsTable_filter label').toggleClass('hideMe');
+}
+
+function constructToolbarForMembershipRequestsTable(){
+ var dispSelectedUserReqs = $('
', {
+ id : 'displaySelectedUserReqs'
+ });
+ dispSelectedUserReqs.append($(' ', {
+ id : 'numOfSelectedRowsUserReqs'
+ }));
+ dispSelectedUserReqs.append($(' ', {
+ id : 'justSelectedTextUserReqs',
+ text : ' selected'
+ }));
+
+ $('#usersManagementPortletContainer .usersRequestsTableToolbarContainer')
+// .addClass('hiddenToolbar')
+ .addClass('shownToolbar')
+ .prop('id', 'usersRequestsTableToolbarContainer')
+ .css('display', 'none')
+ .append($('
', {
+ id : 'acceptSeleced',
+ text : 'Accept Selected'
+ })).append($('
', {
+ id : 'rejectSeleced',
+ text : 'Reject Selected'
+ })).append(dispSelectedUserReqs);
+
+ var div = $('
', {
+ id : 'borderFirstScreen'
+ });
+ div.insertBefore('#usersManagementPortletContainer .usersRequestsTableToolbarContainer');
+}
+
+function constructToolbarForSiteTeamsTable(){
+
+ $('
', {
+ class : 'toolbarHr'
+ }).insertBefore($('.groupTeamsTableToolbarContainer'));
+
+ $('.groupTeamsTableToolbarContainer')
+// .addClass('hiddenToolbar')
+ .addClass('shownToolbar')
+ .css('display', 'none')
+ .prop('id', 'groupTeamsTableToolbarContainer')
+ .append($('
', {
+ id : 'deleteSiteTeam',
+ text : 'Delete Group'
+ })).append($('
', {
+ id : 'editSiteTeam',
+ text : 'Edit Group'
+ }));
+
+ var addSiteTeam = $('
', {
+ id : 'addSiteTeam',
+ text : 'New Group'
+ });
+
+ var newGroupPlusIcon = $(' ',{
+ class: "fa fa-plus-circle",
+ 'aria-hidden':"true",
+ id : 'newGroupPlusIcon'
+ });
+
+ addSiteTeam.append(newGroupPlusIcon);
+
+ addSiteTeam.insertAfter('.groupTeamsTableToolbarContainer');
+
+ var div = $('
', {
+ id : 'borderFirstScreen'
+ });
+
+ div.insertBefore('#groupTeamsTableToolbarContainer .groupTeamsTableToolbarContainer');
+}
+
+function constructToolbarForRejectedUsersRequestsTable(){
+ $("div.rejectedMembershipRequestsTableToolbarContainer")
+ .append($('
', {
+ class : 'toolbarHr'
+ }));
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/ajaxcallpost.js b/src/main/webapp/js/ajaxcallpost.js
new file mode 100644
index 0000000..c8b6726
--- /dev/null
+++ b/src/main/webapp/js/ajaxcallpost.js
@@ -0,0 +1,556 @@
+function surroundObjectPropWithDiv(object){
+ for(var prop in object){
+ if(object[prop] instanceof Date)continue;
+ if(object[prop].length === 0)object[prop] = "-";
+ if(object[prop] === '123')object[prop] = "";
+ if(Array.isArray(object[prop]) && object[prop].length > 0){
+ var variable = "";
+ for(var i = 0; i < object[prop].length; i++){
+ if(i===object[prop].length-1){
+ variable += object[prop][i];
+ }else{
+ variable += object[prop][i] + ", ";
+ }
+ }
+ object[prop] = variable;
+ }
+ object[prop] = '' + object[prop] + '
';
+ }
+ return object;
+}
+
+function AJAX_CALL_POST(theData, callBack){
+ $.ajax(
+ {
+ url: loginURL,
+ type: 'post',
+ datatype:'json',
+ data: theData,
+ success: function(data){
+ callBack(data);
+ removeArrowFromFirstTableColumn();
+// hideTeamManagementToolbar();
+ hidePreloader();
+ },
+ error: function (xhr, ajaxOptions, thrownError) {
+ $('div.modal.fade').modal('hide');
+ hidePreloader();
+ $('#usersManagementPortletContainer #InternalServerErrorModal').modal('show');
+// alert(xhr.status);
+// alert(thrownError);
+ }
+ }
+ );
+}
+
+function dataToBeSendViaAJAX(fieldName, value, theObject){
+ var returnObject;
+ if(typeof theObject === "object"){
+ returnObject = theObject;
+ }else {
+ returnObject = {};
+ }
+
+ returnObject[nameSpace + fieldName] = value;
+
+ return returnObject;
+}
+
+function fetchAllCurrentUsers(mode, deleteUsers, usersUUIDs, roles, teams, deleteRoles, reqIDs, sendEmail, typeOfChangesUpponUserMode){
+ var theData = dataToBeSendViaAJAX("groupId", theGroupId);
+ theData = dataToBeSendViaAJAX("currentUsersTable", true, theData);
+ theData = dataToBeSendViaAJAX("deleteUsersFromCurrentUsersTable", deleteUsers, theData);
+ theData = dataToBeSendViaAJAX("sendDismissalEmail", sendEmail, theData);
+ theData = dataToBeSendViaAJAX("selectedUsers", usersUUIDs, theData);
+ theData = dataToBeSendViaAJAX("usersRoles", roles, theData);
+ theData = dataToBeSendViaAJAX("usersTeams", teams, theData);
+ theData = dataToBeSendViaAJAX("modeCurrentUsersTable", mode, theData);
+ theData = dataToBeSendViaAJAX("deletePreviousRoles", deleteRoles, theData);
+ theData = dataToBeSendViaAJAX("membershipRequestsIDs", reqIDs, theData);
+ theData = dataToBeSendViaAJAX("typeOfChangesUpponUserMode", typeOfChangesUpponUserMode, theData);
+
+ var callBack = function(data){
+ var content= JSON.parse(data);
+ currentUsers = [];
+ currentUsers = content.currentUsers;
+
+ if(!$.isEmptyObject(content)){
+ for(var i = 0; i < currentUsers.length; i++){
+ currentUsers[i] = new currentUsersObjectForDataTable(
+ ' ',
+ currentUsers[i].userName,
+ currentUsers[i].userEmail,
+ currentUsers[i].userFullName,
+ currentUsers[i].userSiteRoles,
+ currentUsers[i].userTeams,
+ currentUsers[i].userId,
+ currentUsers[i].requestDate,
+ currentUsers[i].validationDate,
+ currentUsers[i].reqID,
+ currentUsers[i].isSelf,
+ currentUsers[i].RequestDateObject,
+ currentUsers[i].ValidationDateObject
+ );
+ currentUsers[i] = surroundObjectPropWithDiv(currentUsers[i]);
+ }
+
+ $('table#CurrentUsersTable').DataTable().clear();
+ for(var i = 0; i < currentUsers.length; i++){
+ $('table#CurrentUsersTable').dataTable().fnAddData(currentUsers[i]);
+ }
+ $('table#CurrentUsersTable th:first').removeClass('none');
+
+ if(CurrentUsersTablePages <= 1){
+ $('#CurrentUsersTable_paginate').addClass('hidden');
+ }else{
+ $('#CurrentUsersTable_paginate').removeClass('hidden');
+ }
+
+ var isDataTable = $.fn.DataTable.isDataTable( '#GroupTeamsTable' );
+// var siteTeamsTableDataLenght = $('#GroupTeamsTable').dataTable().fnGetData().length;
+ if(mode !== 2 && isDataTable){//refresh
+ fetchAllSiteTeamsForTheCurrentGroup();
+ }else if(mode !== 2 && !isDataTable){
+ initializeGroupTeamsTable();
+ fetchAllSiteTeamsForTheCurrentGroup();
+ searchInputFixForSiteTeamsEditTable();
+ siteTeamsTableEvents();
+ constructToolbarForSiteTeamsTable();
+ initializeSiteTeamUsersTable();
+ searchInputFixForSiteTeamsUsersTable();
+
+ setTimeout(function(){//If you don't add some time interval, the table won't redraw when you press the tab
+ $('table#GroupTeamsTable').DataTable().columns.adjust().draw();
+ $('table#GroupTeamsTable').DataTable().columns.adjust().responsive.recalc();
+
+ removeArrowFromFirstTableColumn();
+ },200);
+ $('.unhit').removeClass('unhit').addClass('redraw');
+ $('li.redraw').on('click', function(){
+ setTimeout(function(){//If you don't add some time interval, the table won't redraw when you press the tab
+ $('table#GroupTeamsTable').DataTable().columns.adjust().draw();
+ $('table#GroupTeamsTable').DataTable().columns.adjust().responsive.recalc();
+
+ removeArrowFromFirstTableColumn();
+ },200);
+ $(this).removeClass('redraw');
+ });
+ }
+
+ } else {
+ hidePreloader();
+ $('#usersManagementPortletContainer #InternalServerErrorModal').modal('show');
+ }
+ };
+
+ AJAX_CALL_POST(theData, callBack);
+}
+
+function fetchAllUsersRequests(mode, reqIDs, replyUserId, sendCustomRejectionEmail, CustomRejectionEmailBody){
+ var theData = dataToBeSendViaAJAX("groupId", theGroupId);
+ theData = dataToBeSendViaAJAX("fetchUsersRequests", true, theData);
+ theData = dataToBeSendViaAJAX("modeMembershipRequestsTable", mode, theData);
+ theData = dataToBeSendViaAJAX("membershipRequestsIds", reqIDs, theData);
+ theData = dataToBeSendViaAJAX("managerId", replyUserId, theData);
+ theData = dataToBeSendViaAJAX("CustomRejectionEmailFromAdmin", sendCustomRejectionEmail, theData);
+ theData = dataToBeSendViaAJAX("CustomRejectionEmailBodyFromAdmin", CustomRejectionEmailBody, theData);
+
+ var callBack = function(data){
+ var content= JSON.parse(data);
+ currentUsersRequests = [];
+ currentUsersRequests = content.currentUsersRequests;
+
+ if(!$.isEmptyObject(content) && content !== null && content.currentUsersRequests.length!== 0){
+ for(var i = 0; i < currentUsersRequests.length; i++){
+ currentUsersRequests[i] = new usersRequestObjectForDataTable(
+ ' ',
+ currentUsersRequests[i].userName,
+ currentUsersRequests[i].userEmail,
+ currentUsersRequests[i].userFullName,
+ currentUsersRequests[i].requestComments,
+ currentUsersRequests[i].userId,
+ currentUsersRequests[i].requestId,
+ currentUsersRequests[i].requestDate,
+ currentUsersRequests[i].requestDateObject
+ );
+ currentUsersRequests[i] = surroundObjectPropWithDiv(currentUsersRequests[i]);
+ }
+ $('table#usersRequestsTable').DataTable().clear();
+ for(var i = 0; i < currentUsersRequests.length; i++){
+ $('table#usersRequestsTable').dataTable().fnAddData(currentUsersRequests[i]);
+ }
+ $('table#usersRequestsTable th:first').removeClass('none');
+ $('div#usersRequestsTableToolbarContainer').animate({height:'hide'});
+ $('div#usersRequestsTableToolbarContainer').removeClass('openToolbar');
+
+ if(UsersRequestsTablePages <= 1){
+ $('#usersRequestsTable_paginate').addClass('hidden');
+ }else{
+ $('#usersRequestsTable_paginate').removeClass('hidden');
+ }
+
+ countUsersMembershipRequests();
+// $('div#usersRequestsTableToolbarContainer').addClass('hiddenToolbar').removeClass('shownToolbar');
+ $('span#numOfSelectedRowsUserReqs').text('');
+
+ } else if(content.currentUsersRequests.length === 0) {
+ $('#notificationsNumberPlaceHolder').text(0);
+ $('#notificationsNumberPlaceHolderTabletView').text(0);
+ } else if(content === null) {
+ hidePreloader();
+ $('#usersManagementPortletContainer #InternalServerErrorModal').modal('show');
+ }
+
+ fetchAllCurrentUsers(2, false, [], [], [], false, [], false);
+ }
+
+ AJAX_CALL_POST(theData, callBack);
+}
+
+function fetchAllRejectedUsersRequests() {
+ var theData = dataToBeSendViaAJAX("groupId", theGroupId);
+ theData = dataToBeSendViaAJAX("fetchUsersRejectedRequests", true, theData);
+// theData = dataToBeSendViaAJAX("membershipRequestsIds", reqIDs, theData);
+
+ var callback = function(data){
+ var content= JSON.parse(data);
+ var rejectedUsersRequests = [];
+ rejectedUsersRequests = content.currentUsersRequests;
+
+ if(!$.isEmptyObject(content) && content !== null && content.currentUsersRequests.length!== 0){
+ for(var i = 0; i < rejectedUsersRequests.length; i++){
+ rejectedUsersRequests[i] = new rejectedUsersRequestObjectForDataTable(
+ ' ',
+ rejectedUsersRequests[i].userName,
+ rejectedUsersRequests[i].userEmail,
+ rejectedUsersRequests[i].userFullName,
+ rejectedUsersRequests[i].requestComments,
+ rejectedUsersRequests[i].userId,
+ rejectedUsersRequests[i].requestId,
+ rejectedUsersRequests[i].requestDate,
+ rejectedUsersRequests[i].rejectionDate,
+ rejectedUsersRequests[i].requestDateObject,
+ rejectedUsersRequests[i].rejectionDateObject
+ );
+ rejectedUsersRequests[i] = surroundObjectPropWithDiv(rejectedUsersRequests[i]);
+ }
+
+
+ $('table#rejectedUsersRequestsTable').DataTable().clear();
+ for(var i = 0; i < rejectedUsersRequests.length; i++){
+ $('table#rejectedUsersRequestsTable').dataTable().fnAddData(rejectedUsersRequests[i]);
+ }
+// $('table#rejectedUsersRequestsTable th:first').removeClass('none');
+// $('div#usersRequestsTableToolbarContainer').animate({height:'hide'});
+// $('div#usersRequestsTableToolbarContainer').removeClass('openToolbar');
+
+ var numOfTablePages = $('#rejectedUsersRequestsTable').DataTable().page.info().pages;
+ if(numOfTablePages <= 1){
+ $('#rejectedUsersRequestsTable_paginate').addClass('hidden');
+ }else{
+ $('#rejectedUsersRequestsTable_paginate').removeClass('hidden');
+ }
+
+ countUsersMembershipRequests();
+// $('div#usersRequestsTableToolbarContainer').addClass('hiddenToolbar').removeClass('shownToolbar');
+// $('span#numOfSelectedRowsUserReqs').text('');
+
+ } else if(content.currentUsersRequests.length === 0) {
+// $('#notificationsNumberPlaceHolder').text(0);
+// $('#notificationsNumberPlaceHolderTabletView').text(0);
+ } else if(content === null) {
+ hidePreloader();
+ $('#usersManagementPortletContainer #InternalServerErrorModal').modal('show');
+ }
+
+// fetchAllCurrentUsers(2, false, [], [], [], false, [], false);
+ }
+
+ AJAX_CALL_POST(theData, callback);
+}
+
+function countUsersMembershipRequests(){
+ var theData = dataToBeSendViaAJAX("groupId", theGroupId);
+ theData = dataToBeSendViaAJAX("countUsersMembershipRequests", true, theData);
+
+ var callBack = function(data){
+ var content= JSON.parse(data);
+
+ if(!$.isEmptyObject(content) && content !== null){
+ $('#notificationsNumberPlaceHolder').text(content.countUsersMembershipRequests[0]);
+ $('#notificationsNumberPlaceHolderTabletView').text(content.countUsersMembershipRequests[0]);
+ portalName = content.countUsersMembershipRequests[1];//retrieving the portal name from the backend via ajax
+ }else if(content === null) {
+ hidePreloader();
+ $('#usersManagementPortletContainer #InternalServerErrorModal').modal('show');
+ }
+ };
+
+ AJAX_CALL_POST(theData, callBack);
+}
+
+function fetchAllSiteTeamsForTheCurrentGroup(){
+ var theData = dataToBeSendViaAJAX("groupId", theGroupId);
+ theData = dataToBeSendViaAJAX("fetchAllSiteTeamsForTheCurrentGroup", true, theData);
+ theData = dataToBeSendViaAJAX("modeSiteTeams", SITE_TEAMS_TABLE_REFRESH, theData);
+
+ var callBack = function(data){
+ var content= JSON.parse(data);
+ var siteTeams = [];
+ siteTeams = content.siteTeams;
+
+ if(!$.isEmptyObject(content) && content !== null && siteTeams.length!== 0){
+ var teams = [];
+ for(var i = 0; i < siteTeams.length; i++){
+ siteTeams[i] = new siteTeamsObjectForDataTable(
+ ' ',
+ siteTeams[i].Name,
+ siteTeams[i].TeamID,
+ siteTeams[i].Description,
+ siteTeams[i].NumberOfUsers,
+ siteTeams[i].CreationDate,
+ siteTeams[i].LastModificationDate,
+ siteTeams[i].CreatorName,
+ siteTeams[i].siteTeamUsers,
+ siteTeams[i].CreationDateObject,
+ siteTeams[i].LastModificationDateObject
+ );
+ teams.push(siteTeams[i].Name);
+ siteTeams[i].siteTeamUsers = formatSiteTeamUsers(siteTeams[i].siteTeamUsers);
+ siteTeams[i] = surroundObjectPropWithDiv(siteTeams[i]);
+ }
+ teamEditedOrDeleted(teams);
+ $('table#GroupTeamsTable').DataTable().clear();
+ for(var i = 0; i < siteTeams.length; i++){
+ $('table#GroupTeamsTable').dataTable().fnAddData(siteTeams[i]);
+ }
+
+ if(GroupTeamsTablePages <= 1){
+ $('#GroupTeamsTable_paginate').addClass('hidden');
+ } else if(siteTeams.length === 0){
+ $('#GroupTeamsTable_paginate').addClass('hidden');
+ }
+ }else if(content === null) {
+ hidePreloader();
+ $('#usersManagementPortletContainer #InternalServerErrorModal').modal('show');
+ }else if(siteTeams.length === 0){
+ $('#GroupTeamsTable_paginate').removeClass('hidden');
+ }
+ if(!handlersAppliedToToolbarForFirstTime){
+ siteTeamsToolbarEvents();
+ }
+ };
+
+ AJAX_CALL_POST(theData, callBack);
+}
+
+function EditSiteTeamsForTheCurrentGroup(siteTeamName, siteTeamDescription, TeamID){
+ var theData = dataToBeSendViaAJAX("groupId", theGroupId);
+ theData = dataToBeSendViaAJAX("fetchAllSiteTeamsForTheCurrentGroup", true, theData);
+ theData = dataToBeSendViaAJAX("modeSiteTeams", SITE_TEAMS_TABLE_EDIT_GROUP, theData);
+ theData = dataToBeSendViaAJAX("siteTeamName", siteTeamName, theData);
+ theData = dataToBeSendViaAJAX("siteTeamDescription", siteTeamDescription, theData);
+ theData = dataToBeSendViaAJAX("siteTeamID", TeamID, theData);
+
+ var callBack = function(data){
+ var content= JSON.parse(data);
+ var siteTeams = [];
+ siteTeams = content.siteTeams;
+
+ if(!$.isEmptyObject(content) && content !== null && siteTeams.length!== 0){
+ var teams = [];
+ for(var i = 0; i < siteTeams.length; i++){
+ siteTeams[i] = new siteTeamsObjectForDataTable(
+ ' ',
+ siteTeams[i].Name,
+ siteTeams[i].TeamID,
+ siteTeams[i].Description,
+ siteTeams[i].NumberOfUsers,
+ siteTeams[i].CreationDate,
+ siteTeams[i].LastModificationDate,
+ siteTeams[i].CreatorName,
+ siteTeams[i].siteTeamUsers,
+ siteTeams[i].CreationDateObject,
+ siteTeams[i].LastModificationDateObject
+ );
+ teams.push(siteTeams[i].Name);
+ siteTeams[i].siteTeamUsers = formatSiteTeamUsers(siteTeams[i].siteTeamUsers);
+ siteTeams[i] = surroundObjectPropWithDiv(siteTeams[i]);
+ }
+ teamEditedOrDeleted(teams);
+ $('table#GroupTeamsTable').DataTable().clear();
+ for(var i = 0; i < siteTeams.length; i++){
+ $('table#GroupTeamsTable').dataTable().fnAddData(siteTeams[i]);
+ }
+ $('#groupTeamsTableToolbarContainer').animate({height:'hide'});
+ $('#groupTeamsTableToolbarContainer').removeClass('opened');
+
+ fetchAllCurrentUsers(2, false, [], [], false, [], false);
+ }else if(content === null) {
+ $('div.modal.fade').modal('hide');
+ hidePreloader();
+ $('#usersManagementPortletContainer #InternalServerErrorModal').modal('show');
+ }
+ $('#editGroupTeamModal').modal('hide');
+ };
+
+ showPreloader();
+ AJAX_CALL_POST(theData, callBack);
+}
+
+function CreateSiteTeamsForTheCurrentGroup(siteTeamName, siteTeamDescription){
+ var theData = dataToBeSendViaAJAX("groupId", theGroupId);
+ theData = dataToBeSendViaAJAX("fetchAllSiteTeamsForTheCurrentGroup", true, theData);
+ theData = dataToBeSendViaAJAX("modeSiteTeams", SITE_TEAMS_TABLE_CREATE_GROUP, theData);
+ theData = dataToBeSendViaAJAX("siteTeamName", siteTeamName, theData);
+ theData = dataToBeSendViaAJAX("siteTeamDescription", siteTeamDescription, theData);
+
+ var callBack = function(data){
+ var content= JSON.parse(data);
+ var siteTeams = [];
+ siteTeams = content.siteTeams;
+
+ if(!$.isEmptyObject(content) && content !== null && siteTeams.length!== 0){
+ var teams = [];
+ for(var i = 0; i < siteTeams.length; i++){
+ siteTeams[i] = new siteTeamsObjectForDataTable(
+ ' ',
+ siteTeams[i].Name,
+ siteTeams[i].TeamID,
+ siteTeams[i].Description,
+ siteTeams[i].NumberOfUsers,
+ siteTeams[i].CreationDate,
+ siteTeams[i].LastModificationDate,
+ siteTeams[i].CreatorName,
+ siteTeams[i].siteTeamUsers,
+ siteTeams[i].CreationDateObject,
+ siteTeams[i].LastModificationDateObject
+ );
+ teams.push(siteTeams[i].Name);
+ siteTeams[i].siteTeamUsers = formatSiteTeamUsers(siteTeams[i].siteTeamUsers);
+ siteTeams[i] = surroundObjectPropWithDiv(siteTeams[i]);
+ }
+ teamEditedOrDeleted(teams);
+ $('table#GroupTeamsTable').DataTable().clear();
+ for(var i = 0; i < siteTeams.length; i++){
+ $('table#GroupTeamsTable').dataTable().fnAddData(siteTeams[i]);
+ }
+ fetchAllCurrentUsers(2, false, [], [], false, [], false);
+ }else if(content === null) {
+ $('div.modal.fade').modal('hide');
+ hidePreloader();
+ $('#usersManagementPortletContainer #InternalServerErrorModal').modal('show');
+ }
+ $('#newGroupTeamModal').modal('hide');
+ };
+
+ $('div.modal.fade').modal('hide');
+ showPreloader();
+ AJAX_CALL_POST(theData, callBack);
+}
+
+function DeleteSiteTeamsForTheCurrentGroup(teamID){
+ var theData = dataToBeSendViaAJAX("groupId", theGroupId);
+ theData = dataToBeSendViaAJAX("fetchAllSiteTeamsForTheCurrentGroup", true, theData);
+ theData = dataToBeSendViaAJAX("modeSiteTeams", SITE_TEAMS_TABLE_DELETE_GROUP, theData);
+ theData = dataToBeSendViaAJAX("siteTeamID", teamID, theData);
+
+ var callBack = function(data){
+ var content= JSON.parse(data);
+ var siteTeams = [];
+ siteTeams = content.siteTeams;
+
+ if(!$.isEmptyObject(content) && content !== null){
+ var teams = [];
+ for(var i = 0; i < siteTeams.length; i++){
+ siteTeams[i] = new siteTeamsObjectForDataTable(
+ ' ',
+ siteTeams[i].Name,
+ siteTeams[i].TeamID,
+ siteTeams[i].Description,
+ siteTeams[i].NumberOfUsers,
+ siteTeams[i].CreationDate,
+ siteTeams[i].LastModificationDate,
+ siteTeams[i].CreatorName,
+ siteTeams[i].siteTeamUsers,
+ siteTeams[i].CreationDateObject,
+ siteTeams[i].LastModificationDateObject
+ );
+ teams.push(siteTeams[i].Name);
+ siteTeams[i].siteTeamUsers = formatSiteTeamUsers(siteTeams[i].siteTeamUsers);
+ siteTeams[i] = surroundObjectPropWithDiv(siteTeams[i]);
+ }
+ teamEditedOrDeleted(teams);
+
+ if(siteTeams.length!== 0){
+ $('table#GroupTeamsTable').DataTable().clear();
+ } else {
+ $('table#GroupTeamsTable').DataTable().clear().draw();
+ }
+
+ for(var i = 0; i < siteTeams.length; i++){
+ $('table#GroupTeamsTable').dataTable().fnAddData(siteTeams[i]);
+ }
+ $('#groupTeamsTableToolbarContainer').animate({height:'hide'});
+ $('#groupTeamsTableToolbarContainer').removeClass('opened');
+
+ fetchAllCurrentUsers(2, false, [], [], false, [], false);
+ }else if(content === null) {
+ $('div.modal.fade').modal('hide');
+ hidePreloader();
+ $('#usersManagementPortletContainer #InternalServerErrorModal').modal('show');
+ }
+ $('#deleteGroupTeamModal').modal('hide');
+ };
+
+ showPreloader();
+ AJAX_CALL_POST(theData, callBack);
+}
+
+function fetchUserRequestRejectionEmailSubject() {
+ var theData = dataToBeSendViaAJAX("groupId", theGroupId);
+ theData = dataToBeSendViaAJAX("userRequestRejectionEmailSubject", true, theData);
+
+ var callback = function(data){
+ var objectFromJSON = JSON.parse(data);
+ rejectRequestEmailSubject = objectFromJSON.userRequestRejectionEmailSubject;
+ userRequestRejectionEmailAdminsMailsCC = eval(objectFromJSON.userRequestRejectionEmailAdminsMailsCC);
+ $('.emailSubject').text(rejectRequestEmailSubject);
+
+ $('textarea#BCCAdminsEmails').textext()[0].tags().addTags(
+ userRequestRejectionEmailAdminsMailsCC);
+ };
+
+ AJAX_CALL_POST(theData, callback);
+}
+
+function fetchRolesInitial() {
+ var theData = dataToBeSendViaAJAX("rolesInitial", true, theData);
+
+ var callback = function(data){
+ theList = JSON.parse(data).roleNames;
+ fetchTeamsInitial();
+ };
+
+ AJAX_CALL_POST(theData, callback);
+}
+
+function fetchTeamsInitial() {
+ var theData = dataToBeSendViaAJAX("teamsInitial", true, theData);
+
+ var callback = function(data){
+ teamList = JSON.parse(data).teamNames;
+ $.each(teamList, function(index, value){
+ teamList[index] = value.replace("'", "'");
+ });
+
+ tagEvents(theList, teamList);
+
+ tableEvents();
+ fetchUserRequestRejectionEmailSubject();
+
+ hidePreloader();
+ };
+
+ AJAX_CALL_POST(theData, callback);
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/bootstrap.min.js b/src/main/webapp/js/bootstrap.min.js
new file mode 100644
index 0000000..848258d
--- /dev/null
+++ b/src/main/webapp/js/bootstrap.min.js
@@ -0,0 +1,6 @@
+/*!
+* Bootstrap.js by @fat & @mdo
+* Copyright 2013 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('
').insertBefore(e(this)).on("click",r),s.toggleClass("open")),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f ').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:''}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:' ',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
\ No newline at end of file
diff --git a/src/main/webapp/js/datatableutils.js b/src/main/webapp/js/datatableutils.js
new file mode 100644
index 0000000..f56ab7d
--- /dev/null
+++ b/src/main/webapp/js/datatableutils.js
@@ -0,0 +1,151 @@
+function tableUtils(){
+
+}
+
+function startPreloader(){
+ $("#element").introLoader();
+}
+
+function stopPreloader(){
+ $("#element").data('introLoader').stop();
+}
+
+function currentUsersObjectForDataTable(
+ CheckBox, UserName,
+ Email, FullName,
+ Roles, Teams, UserId,
+ RequestDate, ValidationDate,
+ reqID,
+ isSelf, RequestDateObject,
+ ValidationDateObject){
+ this.CheckBox = CheckBox;
+ this.UserName = UserName;
+ this.Email = Email;
+ this.FullName = FullName;
+ this.Roles = Roles;
+ this.Teams = Teams;
+ this.UserId = UserId;
+ this.RequestDate = RequestDate;//returnDateInProperForm(RequestDate);
+ this.ValidationDate = ValidationDate;//returnDateInProperForm(ValidationDate);
+ this.reqID = reqID;
+ this.isSelf = isSelf;
+
+ if( RequestDateObject === '-' ) {
+ this.RequestDateObject = new Date(-8640000000000000);
+ }else
+ this.RequestDateObject = new Date(RequestDateObject);
+
+ if( ValidationDateObject === '-' ) {
+ this.ValidationDateObject = new Date(-8640000000000000);
+ }else
+ this.ValidationDateObject = new Date(ValidationDateObject);
+}
+
+function usersRequestObjectForDataTable(
+ CheckBox, UserName,
+ Email, FullName,
+ Message, UserId,
+ RequestId, RequestDate,
+ RequestDateObject){
+ this.CheckBox = CheckBox;
+ this.UserName = UserName;
+ this.Email = Email;
+ this.FullName = FullName;
+ this.Message = Message;
+ this.UserId = UserId;
+ this.RequestDate = RequestDate;//returnDateInProperForm(RequestDate);
+ this.RequestId = RequestId;
+
+ if( RequestDateObject === '-' ) {
+ this.RequestDateObject = new Date(-8640000000000000);
+ }else
+ this.RequestDateObject = new Date(RequestDateObject);
+}
+
+function rejectedUsersRequestObjectForDataTable(
+ CheckBox, UserName,
+ Email, FullName,
+ Message, UserId,
+ RequestId, RequestDate, RejectionDate,
+ RequestDateObject, RejectionDateObject){
+ this.CheckBox = CheckBox;
+ this.UserName = UserName;
+ this.Email = Email;
+ this.FullName = FullName;
+ this.Message = Message;
+ this.UserId = UserId;
+ this.RequestId = RequestId;
+ this.RequestDate = RequestDate;//returnDateInProperForm(RequestDate);
+ this.RejectionDate = RejectionDate;//returnDateInProperForm(RejectionDate);
+
+ if( RequestDateObject === '-' ) {
+ this.RequestDateObject = new Date(-8640000000000000);
+ }else
+ this.RequestDateObject = new Date(RequestDateObject);
+
+ if( RejectionDateObject === '-' ) {
+ this.RejectionDateObject = new Date(-8640000000000000);
+ }else
+ this.RejectionDateObject = new Date(RejectionDateObject);
+}
+
+function siteTeamsObjectForDataTable(
+ CheckBox, Name, TeamID,
+ Description, NumberOfUsers,
+ CreationDate, LastModificationDate,
+ CreatorName, siteTeamUsers,
+ CreationDateObject, LastModificationDateObject){
+ this.CheckBox = CheckBox;
+ this.Name = Name;
+ this.TeamID = TeamID;
+ this.Description = Description;
+ this.NumberOfUsers = NumberOfUsers;
+ this.CreationDate = /*returnDateInProperForm(*/CreationDate;
+ this.LastModificationDate = /*returnDateInProperForm(*/LastModificationDate;
+ this.CreatorName = CreatorName;
+ this.siteTeamUsers = siteTeamUsers;
+
+ if( CreationDateObject === '-' ) {
+ this.CreationDateObject = new Date(-8640000000000000);
+ }else
+ this.CreationDateObject = new Date(CreationDateObject);
+
+ if( LastModificationDateObject === '-' ) {
+ this.LastModificationDateObject = new Date(-8640000000000000);
+ }else
+ this.LastModificationDateObject = new Date(LastModificationDateObject);
+}
+
+function siteTeamsUserObjectForDataTable(
+ FullName,UserName){
+
+ this.FullName = FullName;
+ this.UserName = UserName;
+}
+
+function returnDateInProperForm(ValidationDate){
+ if(ValidationDate !== null && ValidationDate !== undefined && ValidationDate !== '-'){
+ var serverDate = new Date(Date.parse(ValidationDate));
+ var properDate;
+ var month = serverDate.getMonth()+1;
+ month = (month.toString().length === 1) ? '0'+month : month;
+ var day = serverDate.getDate().toString();
+ day = (day.length === 1) ? '0'+day : day;
+ var year = serverDate.getFullYear();
+ properDate = month + '/' + day + '/' + year;
+
+ return properDate;
+ }else return '-';
+}
+
+function formatSiteTeamUsers(array){
+ var newArray = [];
+ for(var i=0; i" + array[i].fullName + "
" + "," + array[i].screenName + "
";
+ newArray.push(array[i]);
+ }
+ }
+
+ return newArray;
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/displayRolesOnHOver.js b/src/main/webapp/js/displayRolesOnHOver.js
new file mode 100644
index 0000000..3f9fbec
--- /dev/null
+++ b/src/main/webapp/js/displayRolesOnHOver.js
@@ -0,0 +1,192 @@
+function displaySiteRolesOnHover(){
+ $('#usersManagementPortletContainer #CurrentUsersTable_wrapper').on('mouseover', function(e){
+ var tagName = $(e.target).prop('tagName').toLowerCase();
+ var parentTagName = $(e.target).parent().prop('tagName').toLowerCase();
+ var className = $(e.target).attr('class');
+ if(tagName === "td" || parentTagName === "td" || tagName === "span" || tagName === "ul" || tagName === "li"){
+ return;
+ }else {
+ destroyBubble();
+ }
+ });
+
+ $('#usersManagementPortletContainer table#CurrentUsersTable, table#usersRequestsTable, table#rejectedUsersRequestsTable')
+ .off('mouseover').on('mouseover', 'tr td:not(:first) div', function(e){
+ destroyBubble();
+ var tagName = $(e.target).prop('tagName').toLowerCase();
+
+ if(isEllipsisActive($(this))){
+ $(this).closest('td').addClass('relative');
+
+ var one = $(' ', {
+ id : 'rolesArrow'
+ });
+ var two = $(' ', {
+ id : 'onHoverUser',
+ 'class' : 'rolesBubbletittle'
+ });
+ var three = $(' ', {
+ id : 'elementBubbletittle'
+// text : 'Roles of'
+ });
+
+ var four = $(' ', {
+ id : 'roles'
+ });
+
+ var five = $(' ', {
+ id : 'rolesContainer'
+ });
+
+ four.append(three).append(two).append(one);
+ five.append(four);
+ $(this).closest('td').append(five);
+
+
+ var text = $(this).text();
+ var splitted = text.split(',');
+
+ var theUl = $('', {
+ id : 'rolesList'
+ });
+
+ $('#roles').append(theUl);
+
+ for(var string in splitted){
+ $('#rolesList').append($(' ', {
+ text : splitted[string]
+ }));
+ }
+ var thIndex = $(this).closest('td').index() + 1;
+ var tableId = $(this).closest('table').attr('id');
+ var title = $('#' + tableId.toString() + ' th:nth-child(' + thIndex + ')').text();
+ $('#onHoverUser').text(title);
+ }
+ }).on('click', 'tr td:not(:first-of-type) div', function(){
+ var table = $(this).closest('table');
+ var tr = $(this).closest('tr');
+ var index = $(this).closest('td').index() + 1;
+ var data = table.dataTable().fnGetData(tr[0]);
+ $('#userDetailsModal .modal-body').html('');
+ $('span#userName').text('');
+ var tableId = table.attr('id');
+ if(tableId === 'CurrentUsersTable'){
+ $('#openEditModal').removeClass('hidden');
+ passUsersDetailsToModalFromCurrentUsersTable(tableId, data);
+ }else if(tableId === 'usersRequestsTable'){
+ $('#openEditModal').addClass('hidden');
+ passUsersDetailsToModalFromUsersRequestsTable(tableId, data);
+
+ }
+
+ if($(this).closest('table').attr('id') === 'CurrentUsersTable'){
+// console.log('The index of the row is: ' + );
+ var thisTrIndex = $('#CurrentUsersTable tbody tr').index($(this).closest('tr'));
+ keepTrackOfUsersTableRow = thisTrIndex;
+ }
+ });
+}
+
+function destroyBubble(){
+ $('#rolesList').remove();
+ $('#usersManagementPortletContainer .relative').removeClass('relative');
+ $('#rolesContainer').remove();
+}
+
+function isEllipsisActive(element) {
+ return_val = false;
+ var text = element.text();
+ var html = $(' ',{
+ text : text,
+ id : "tmpsmp"
+ });
+
+ $('body').append(html);
+
+ if(element.width() < html.width()) {
+ return_val = true;
+ }
+
+ $('#tmpsmp').remove();
+
+ return return_val;
+}
+
+function removeDivTag(element){
+ var text = $($.parseHTML(element)).text();
+ return text;
+}
+
+function passUsersDetailsToModalFromCurrentUsersTable(tableId, data){
+ var i=0;
+ $('span#userName').text(removeDivTag(data.UserName));
+
+ for(var field in data){
+ if(i === 0 || i === 6 || i === 9 || i === 11) {
+ i++;
+ continue;
+ } else {
+ i++;
+ }
+ injectDetailsToModal(tableId, i, removeDivTag(data[field]));
+ }
+
+ usersRequestsDetailModaWasOpen = false;
+ $('#userDetailsModal').modal('show');
+}
+
+function passUsersDetailsToModalFromUsersRequestsTable(tableId, data){
+ var i=0;
+ $('span#userName').text(removeDivTag(data.UserName));
+ for(var field in data){
+ if(i === 0 || i === 5 || i === 7) {
+ i++;
+ continue;
+ } else {
+ i++;
+ }
+ injectDetailsToModal(tableId, i, removeDivTag(data[field]));
+ }
+
+ $('#usersRequestsModal').modal('hide');
+ usersRequestsDetailModaWasOpen = true;
+ $('#userDetailsModal').modal('show');
+}
+
+function injectDetailsToModal(tableId, thNumber, text){
+ if(thNumber === 8){
+ thNumber = 7;
+ }else if(thNumber === 9){
+ thNumber = 8;
+ }else if(thNumber === 11){
+ thNumber = 9;
+ }else if(thNumber === 7){
+ thNumber = 6;
+ }
+
+ var properTitle = $('#' + tableId + ' th:nth-child(' + thNumber + ')').text();
+ buildRowsForModal(properTitle, text);
+}
+
+function buildRowsForModal(properTitle, text){
+ //Label for attribute must equal the div id
+ var randomNumber = Math.floor(Math.random() * 2323614) + 1;
+ var row = $('
', {
+ 'class' : 'row-fluid'
+ });
+ var theId = 'randomId'+randomNumber;
+ var userField = $('
', {
+ id : theId,
+ text : text,
+ 'class' : 'span9'
+ });
+ var label = $(' ', {
+ 'for' : theId,
+ 'class' : 'span3',
+ text : properTitle + ':'
+ });
+
+ row.append(label).append(userField);
+
+ $('#userDetailsModal .modal-body').append(row);
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/groupTeamsModal.js b/src/main/webapp/js/groupTeamsModal.js
new file mode 100644
index 0000000..4250cc1
--- /dev/null
+++ b/src/main/webapp/js/groupTeamsModal.js
@@ -0,0 +1,51 @@
+$('.modal-footer button#assignUsersToGroupModalBtn').off('click').on('click',function(){
+ assignUsersToGroupModalBtnPressed = true;
+ filterUserTableByUsersThatDontBelongInAGroup = true;
+
+ $('#CurrentUsersTable_filter label input:first').animate({height:'show'});
+ $('#CurrentUsersTable_filter .searchDiv').addClass('active');
+
+ $('#displayGroupTeamUsersModal').modal('hide');
+// An event triggers on modal hidden and drives you to code on line 38
+ //view $('#displayGroupTeamUsersModal').on('hidden', function(){
+
+// var modalHide = function(){
+// $('#displayGroupTeamUsersModal').modal('hide');
+// };
+//
+// var showAfterModalHides = function(){
+// setTimeout(function(){
+// $('#CurrentUsersTable_filter label').removeClass('hideMe');
+// $('#CurrentUsersTable_filter label input').focus();
+// $('li#userManagement a.tabTitle').tab('show');//tab('show') applies on data-toggle="tab" element, only
+// },700);
+// };
+//
+// var aMhI = afterModalHidesItself(modalHide);
+//
+// aMhI.done(showAfterModalHides);
+});
+
+function afterModalHidesItself(fn, time){
+ var dfd = $.Deferred();
+
+ setTimeout(function(){
+ dfd.resolve(fn());
+ }, time || 0);
+
+ return dfd.promise();
+}
+
+$('#displayGroupTeamUsersModal').on('hidden', function(){
+ if(assignUsersToGroupModalBtnPressed){
+ $('#CurrentUsersTable_filter label').removeClass('hideMe');
+ $('#tagsForWhenYouWantToAssignUsersToGroups').tagsinput('removeAll');
+ $('#tagsForWhenYouWantToAssignUsersToGroups').tagsinput('add', $.trim($('#teamNameHeader').text()));
+ var regex = '^((?!' + $.trim($('#teamNameHeader').text()) + ').)*$';
+
+ $('table#CurrentUsersTable').DataTable().columns( 5 ).search(regex, true, false).draw();
+ $('table#CurrentUsersTable th:first').removeClass('sorting_asc');
+ $('li#userManagement a.tabTitle').tab('show');//tab('show') applies on data-toggle="tab" element, only
+ assignUsersToGroupModalBtnPressed = false;
+ }
+});
\ No newline at end of file
diff --git a/src/main/webapp/js/jquery-textext-master/.gitignore b/src/main/webapp/js/jquery-textext-master/.gitignore
new file mode 100644
index 0000000..4119c82
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/.gitignore
@@ -0,0 +1,4 @@
+/node_modules/
+/src/css/_common.css
+tests/*.png
+tests/*.jar
\ No newline at end of file
diff --git a/src/main/webapp/js/jquery-textext-master/MIT-LICENSE b/src/main/webapp/js/jquery-textext-master/MIT-LICENSE
new file mode 100644
index 0000000..ade171f
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2011-2012 Alex Gorbatchev
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/main/webapp/js/jquery-textext-master/README.md b/src/main/webapp/js/jquery-textext-master/README.md
new file mode 100644
index 0000000..d5e44e5
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/README.md
@@ -0,0 +1,132 @@
+# Version 2.0 in development
+
+Please note that there's version 2.0 in active development. Checkout the #2.0.0-wip
+branch for more fun action! CoffeeScript and full Jasmine coverage - fun stuff!
+
+## About
+
+TextExt is a plugin for jQuery which is designed to provide functionality such
+as tag input and autocomplete.
+
+The core design principle behind TextExt is modularity and extensibility. Each
+piece of functionality is separated from the main core and can act individually
+or together with other plugins.
+
+TextExt's modular design allows you easily turn a standard HTML text input into a
+wide range of modern, tailored to your needs input field without bloating your
+source code and slowing down your site with the code that you aren't using.
+
+A wide number of plugins are available including Tags, Autocomplete, Filter, Ajax
+as well as a few which are purely aesthetic like Focus.
+
+Please refer to the [manual] for the full API documentation and examples.
+
+## Features
+
+* Tags
+* Autocomplete
+* AJAX loading
+* Placeholder text
+* Arrow
+* ... and much more!
+
+## Example
+```html
+
+
+
+```
+
+## How To Use
+
+The steps to using TextExt are as follows:
+
+1. Specify which plugins you need via the `plugins` option
+2. Configure each plugin individually if necessary
+3. Enjoy!
+
+## History
+
+### 1.3.1
+
+#### Bug Fixes
+* Fixed jQuery 1.8.x compatability ([issue #74](https://github.com/alexgorbatchev/jquery-textext/issues/74)).
+
+### 1.3.0
+
+#### New Features
+* Added `tagClick` event to the tags plugin
+ ([issue #13](https://github.com/alexgorbatchev/jquery-textext/pull/13)).
+ See the [example](http://textextjs.com/manual/examples/tags-click.html).
+* Prompt plugin now checks `placeholder` attribute
+ ([issue #8](https://github.com/alexgorbatchev/jquery-textext/pull/8)).
+ See the [example](http://textextjs.com/manual/examples/prompt-from-placeholder.html).
+* Clicking on item in autocomplete will automatically add that item to tags
+ [#2](https://github.com/alexgorbatchev/jquery-textext/issues/2).
+
+#### Bug Fixes
+* Fixes getter methods created when plugins are initialized.
+ ([issue #20](https://github.com/alexgorbatchev/jquery-textext/pull/20)).
+* Fixed issues
+ [#4](https://github.com/alexgorbatchev/jquery-textext/issues/4),
+ [#4](https://github.com/alexgorbatchev/jquery-textext/issues/5) and
+ [#10](https://github.com/alexgorbatchev/jquery-textext/issues/5)
+ related to the mouse issues in the autocomplete dropdown.
+* Fixed `textext.[pluginName]()`
+ ([issue #20](https://github.com/alexgorbatchev/jquery-textext/pull/20)).
+
+### 1.2.0
+* Added ability to get instances of plugins to call methods on them directy
+ ([issue #6](https://github.com/alexgorbatchev/jquery-textext/issues/6)).
+ See the [example](http://textextjs.com/manual/examples/tags-adding.html).
+
+### 1.1.0
+
+#### New Features
+* Added `autocomplete.render` option for custom rendering. See the
+ [manual](http://textextjs.com/manual/plugins/autocomplete.html#autocomplete-render) and
+ [example](http://textextjs.com/manual/examples/autocomplete-with-custom-render.html).
+* Added `autocomplete.dropdown.maxHeight` option for setting height of the dropdown. See
+ [manual](http://textextjs.com/manual/plugins/autocomplete.html#autocomplete-dropdown-maxheight) and
+ [example](http://textextjs.com/manual/examples/autocomplete-with-custom-render.html).
+* Added [Arrow plugin](http://textextjs.com/manual/plugins/arrow.html).
+* Switched to MIT license.
+
+#### Bug Fixes
+* TextExt core now works with ` ` tags.
+* Filter plugin now works without Tags.
+* Fixed clicking on suggestion in autocomplete dropdown.
+
+### 1.0.0
+* Initial release.
+
+## License
+
+The TextExt component is released under the open source MIT. This means that you
+can use it any way you want, but I would very much appreciate if you take a minute
+and support the project through a donation.
+
+## Contributors
+
+Alphabetically:
+
+* [adamayres](http://github.com/adamayres) (Adam Ayres)
+* [alexyoung](http://github.com/alexyoung) (Alex Young)
+* [cmer](http://github.com/cmer) (Carl Mercier)
+* [deefour](http://github.com/deefour) (Jason Daly)
+* [KoernerWS](http://github.com/KoernerWS) (Florian Koerner)
+* [sstok](http://github.com/sstok) (Sebastiaan Stok)
+
+[manual]: http://textextjs.com/manual/index.html
+
diff --git a/src/main/webapp/js/jquery-textext-master/bin/stylus b/src/main/webapp/js/jquery-textext-master/bin/stylus
new file mode 100644
index 0000000..1084591
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/bin/stylus
@@ -0,0 +1,3 @@
+#!/bin/bash
+../node_modules/.bin/stylus --watch --include ../src/stylus --out ../src/css ../src/stylus/*.styl
+
diff --git a/src/main/webapp/js/jquery-textext-master/package.json b/src/main/webapp/js/jquery-textext-master/package.json
new file mode 100644
index 0000000..990274b
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/package.json
@@ -0,0 +1,10 @@
+{
+ "name" : "app_name",
+ "version" : "0.0.0",
+ "dependencies" : {
+ "stylus" : "x.x.x",
+ "soda" : ">= 0.2.x",
+ "uglify-js" : "x.x.x"
+ }
+}
+
diff --git a/src/main/webapp/js/jquery-textext-master/src/css/arrow.png b/src/main/webapp/js/jquery-textext-master/src/css/arrow.png
new file mode 100644
index 0000000..4eb8ce7
Binary files /dev/null and b/src/main/webapp/js/jquery-textext-master/src/css/arrow.png differ
diff --git a/src/main/webapp/js/jquery-textext-master/src/css/close.png b/src/main/webapp/js/jquery-textext-master/src/css/close.png
new file mode 100644
index 0000000..154be6b
Binary files /dev/null and b/src/main/webapp/js/jquery-textext-master/src/css/close.png differ
diff --git a/src/main/webapp/js/jquery-textext-master/src/css/textext.core.css b/src/main/webapp/js/jquery-textext-master/src/css/textext.core.css
new file mode 100644
index 0000000..ad3fec0
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/css/textext.core.css
@@ -0,0 +1,29 @@
+.text-core {
+ position: relative;
+}
+.text-core .text-wrap {
+ background: #fff;
+ position: absolute;
+}
+.text-core .text-wrap textarea,
+.text-core .text-wrap input {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-border-radius: 0px;
+ -moz-border-radius: 0px;
+ border-radius: 0px;
+ border: 1px solid #9daccc;
+ outline: none;
+ resize: none;
+ position: absolute;
+ z-index: 1;
+ background: none;
+ overflow: hidden;
+ margin: 0;
+ padding: 3px 5px 4px 5px;
+ white-space: nowrap;
+ font: 11px "lucida grande", tahoma, verdana, arial, sans-serif;
+ line-height: 13px;
+ height: auto;
+}
diff --git a/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.arrow.css b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.arrow.css
new file mode 100644
index 0000000..03e8cda
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.arrow.css
@@ -0,0 +1,13 @@
+.text-core .text-wrap .text-arrow {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 22px;
+ height: 22px;
+ /* background: url("arrow.png") 50% 50% no-repeat; */
+ cursor: pointer;
+ z-index: 2;
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.autocomplete.css b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.autocomplete.css
new file mode 100644
index 0000000..a72fcbd
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.autocomplete.css
@@ -0,0 +1,35 @@
+.text-core .text-wrap .text-dropdown {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ padding: 0;
+ position: absolute;
+ z-index: 3;
+ background: #fff;
+ border: 1px solid #9daccc;
+ width: 100%;
+ max-height: 100px;
+ padding: 1px;
+ font: 11px "lucida grande", tahoma, verdana, arial, sans-serif;
+ display: none;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+.text-core .text-wrap .text-dropdown.text-position-below {
+ margin-top: 1px;
+}
+.text-core .text-wrap .text-dropdown.text-position-above {
+ margin-bottom: 1px;
+}
+.text-core .text-wrap .text-dropdown .text-list .text-suggestion {
+ padding: 3px 5px;
+ cursor: pointer;
+}
+.text-core .text-wrap .text-dropdown .text-list .text-suggestion em {
+ font-style: normal;
+ text-decoration: underline;
+}
+.text-core .text-wrap .text-dropdown .text-list .text-suggestion.text-selected {
+ color: #fff;
+ background: #6d84b4;
+}
diff --git a/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.clear.css b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.clear.css
new file mode 100644
index 0000000..a27b2de
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.clear.css
@@ -0,0 +1,13 @@
+.text-core .text-wrap .text-clear {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ position: absolute;
+ top: 0;
+ right: 14px;
+ width: 22px;
+ height: 22px;
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAYUlEQVR42mP4////P1IwA5RBNIBr+Pj5+/9Tlx78v373OYoCkBgIY2gACWr7tP63CO8BanoBlmyfuQssBsIYGtAVLNpwEsMADA0gAFMIw+hOpEwDSU4i2dMkByvJEUcsAABHaALCQIZDrAAAAABJRU5ErkJggg==") 50% 50% no-repeat;
+ cursor: pointer;
+ z-index: 2;
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.focus.css b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.focus.css
new file mode 100644
index 0000000..9579128
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.focus.css
@@ -0,0 +1,12 @@
+.text-core .text-wrap .text-focus {
+ -webkit-box-shadow: 0px 0px 6px #6d84b4;
+ -moz-box-shadow: 0px 0px 6px #6d84b4;
+ box-shadow: 0px 0px 6px #6d84b4;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ display: none;
+}
+.text-core .text-wrap .text-focus.text-show-focus {
+ display: block;
+}
diff --git a/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.prompt.css b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.prompt.css
new file mode 100644
index 0000000..49eab49
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.prompt.css
@@ -0,0 +1,16 @@
+.text-core .text-wrap .text-prompt {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ margin: 1px 0 0 2px;
+ font: 11px "lucida grande", tahoma, verdana, arial, sans-serif;
+ color: #c0c0c0;
+ overflow: hidden;
+ white-space: pre;
+}
+.text-core .text-wrap .text-prompt.text-hide-prompt {
+ display: none;
+}
diff --git a/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.tags.css b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.tags.css
new file mode 100644
index 0000000..9c5b7a1
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/css/textext.plugin.tags.css
@@ -0,0 +1,49 @@
+.text-core .text-wrap .text-tags {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ padding: 3px 35px 3px 3px;
+ cursor: text;
+}
+.text-core .text-wrap .text-tags.text-tags-on-top {
+ z-index: 2;
+}
+.text-core .text-wrap .text-tags .text-tag {
+ float: left;
+}
+.text-core .text-wrap .text-tags .text-tag .text-button {
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ position: relative;
+ float: left;
+ border: 1px solid #9daccc;
+ background: #e2e6f0;
+ color: #000;
+ padding: 0px 17px 0px 3px;
+ margin: 0 2px 2px 0;
+ cursor: pointer;
+ height: 16px;
+ font: 11px "lucida grande", tahoma, verdana, arial, sans-serif;
+}
+.text-core .text-wrap .text-tags .text-tag .text-button a.text-remove {
+ position: absolute;
+ right: 3px;
+ top: 2px;
+ display: block;
+ width: 11px;
+ height: 11px;
+ background: url("close.png") 0 0 no-repeat;
+}
+.text-core .text-wrap .text-tags .text-tag .text-button a.text-remove:hover {
+ background-position: 0 -11px;
+}
+.text-core .text-wrap .text-tags .text-tag .text-button a.text-remove:active {
+ background-position: 0 -22px;
+}
diff --git a/src/main/webapp/js/jquery-textext-master/src/js/textext.core.js b/src/main/webapp/js/jquery-textext-master/src/js/textext.core.js
new file mode 100644
index 0000000..39cf4d3
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/js/textext.core.js
@@ -0,0 +1,1617 @@
+/**
+ * jQuery TextExt Plugin
+ * http://textextjs.com
+ *
+ * @version 1.3.1
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
+ * @license MIT License
+ */
+(function($, undefined)
+{
+ /**
+ * TextExt is the main core class which by itself doesn't provide any functionality
+ * that is user facing, however it has the underlying mechanics to bring all the
+ * plugins together under one roof and make them work with each other or on their
+ * own.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt
+ */
+ function TextExt() {};
+
+ /**
+ * ItemManager is used to seamlessly convert between string that come from the user input to whatever
+ * the format the item data is being passed around in. It's used by all plugins that in one way or
+ * another operate with items, such as Tags, Filter, Autocomplete and Suggestions. Default implementation
+ * works with `String` type.
+ *
+ * Each instance of `TextExt` creates a new instance of default implementation of `ItemManager`
+ * unless `itemManager` option was set to another implementation.
+ *
+ * To satisfy requirements of managing items of type other than a `String`, different implementation
+ * if `ItemManager` should be supplied.
+ *
+ * If you wish to bring your own implementation, you need to create a new class and implement all the
+ * methods that `ItemManager` has. After, you need to supply your pass via the `itemManager` option during
+ * initialization like so:
+ *
+ * $('#input').textext({
+ * itemManager : CustomItemManager
+ * })
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id ItemManager
+ */
+ function ItemManager() {};
+
+ /**
+ * TextExtPlugin is a base class for all plugins. It provides common methods which are reused
+ * by majority of plugins.
+ *
+ * All plugins must register themselves by calling the `$.fn.textext.addPlugin(name, constructor)`
+ * function while providing plugin name and constructor. The plugin name is the same name that user
+ * will identify the plugin in the `plugins` option when initializing TextExt component and constructor
+ * function will create a new instance of the plugin. *Without registering, the core won't
+ * be able to see the plugin.*
+ *
+ * new in 1.2.0 You can get instance of each plugin from the core
+ * via associated function with the same name as the plugin. For example:
+ *
+ * $('#input').textext()[0].tags()
+ * $('#input').textext()[0].autocomplete()
+ * ...
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtPlugin
+ */
+ function TextExtPlugin() {};
+
+ var stringify = (JSON || {}).stringify,
+ slice = Array.prototype.slice,
+ p,
+ UNDEFINED = 'undefined',
+
+ /**
+ * TextExt provides a way to pass in the options to configure the core as well as
+ * each plugin that is being currently used. The jQuery exposed plugin `$().textext()`
+ * function takes a hash object with key/value set of options. For example:
+ *
+ * $('textarea').textext({
+ * enabled: true
+ * })
+ *
+ * There are multiple ways of passing in the options:
+ *
+ * 1. Options could be nested multiple levels deep and accessed using all lowercased, dot
+ * separated style, eg `foo.bar.world`. The manual is using this style for clarity and
+ * consistency. For example:
+ *
+ * {
+ * item: {
+ * manager: ...
+ * },
+ *
+ * html: {
+ * wrap: ...
+ * },
+ *
+ * autocomplete: {
+ * enabled: ...,
+ * dropdown: {
+ * position: ...
+ * }
+ * }
+ * }
+ *
+ * 2. Options could be specified using camel cased names in a flat key/value fashion like so:
+ *
+ * {
+ * itemManager: ...,
+ * htmlWrap: ...,
+ * autocompleteEnabled: ...,
+ * autocompleteDropdownPosition: ...
+ * }
+ *
+ * 3. Finally, options could be specified in mixed style. It's important to understand that
+ * for each dot separated name, its alternative in camel case is also checked for, eg for
+ * `foo.bar.world` it's alternatives could be `fooBarWorld`, `foo.barWorld` or `fooBar.world`,
+ * which translates to `{ foo: { bar: { world: ... } } }`, `{ fooBarWorld: ... }`,
+ * `{ foo : { barWorld : ... } }` or `{ fooBar: { world: ... } }` respectively. For example:
+ *
+ * {
+ * itemManager : ...,
+ * htmlWrap: ...,
+ * autocomplete: {
+ * enabled: ...,
+ * dropdownPosition: ...
+ * }
+ * }
+ *
+ * Mixed case is used through out the code, wherever it seems appropriate. However in the code, all option
+ * names are specified in the dot notation because it works both ways where as camel case is not
+ * being converted to its alternative dot notation.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExt.options
+ */
+
+ /**
+ * Default instance of `ItemManager` which takes `String` type as default for tags.
+ *
+ * @name item.manager
+ * @default ItemManager
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.options.item.manager
+ */
+ OPT_ITEM_MANAGER = 'item.manager',
+
+ /**
+ * List of plugins that should be used with the current instance of TextExt. The list could be
+ * specified as array of strings or as comma or space separated string.
+ *
+ * @name plugins
+ * @default []
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.options.plugins
+ */
+ OPT_PLUGINS = 'plugins',
+
+ /**
+ * TextExt allows for overriding of virtually any method that the core or any of its plugins
+ * use. This could be accomplished through the use of the `ext` option.
+ *
+ * It's possible to specifically target the core or any plugin, as well as overwrite all the
+ * desired methods everywhere.
+ *
+ * 1. Targeting the core:
+ *
+ * ext: {
+ * core: {
+ * trigger: function()
+ * {
+ * console.log('TextExt.trigger', arguments);
+ * $.fn.textext.TextExt.prototype.trigger.apply(this, arguments);
+ * }
+ * }
+ * }
+ *
+ * 2. Targeting individual plugins:
+ *
+ * ext: {
+ * tags: {
+ * addTags: function(tags)
+ * {
+ * console.log('TextExtTags.addTags', tags);
+ * $.fn.textext.TextExtTags.prototype.addTags.apply(this, arguments);
+ * }
+ * }
+ * }
+ *
+ * 3. Targeting `ItemManager` instance:
+ *
+ * ext: {
+ * itemManager: {
+ * stringToItem: function(str)
+ * {
+ * console.log('ItemManager.stringToItem', str);
+ * return $.fn.textext.ItemManager.prototype.stringToItem.apply(this, arguments);
+ * }
+ * }
+ * }
+ *
+ * 4. And finally, in edge cases you can extend everything at once:
+ *
+ * ext: {
+ * '*': {
+ * fooBar: function() {}
+ * }
+ * }
+ *
+ * @name ext
+ * @default {}
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.options.ext
+ */
+ OPT_EXT = 'ext',
+
+ /**
+ * HTML source that is used to generate elements necessary for the core and all other
+ * plugins to function.
+ *
+ * @name html.wrap
+ * @default ''
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.options.html.wrap
+ */
+ OPT_HTML_WRAP = 'html.wrap',
+
+ /**
+ * HTML source that is used to generate hidden input value of which will be submitted
+ * with the HTML form.
+ *
+ * @name html.hidden
+ * @default ' '
+ * @author agorbatchev
+ * @date 2011/08/20
+ * @id TextExt.options.html.hidden
+ */
+ OPT_HTML_HIDDEN = 'html.hidden',
+
+ /**
+ * Hash table of key codes and key names for which special events will be created
+ * by the core. For each entry a `[name]KeyDown`, `[name]KeyUp` and `[name]KeyPress` events
+ * will be triggered along side with `anyKeyUp` and `anyKeyDown` events for every
+ * key stroke.
+ *
+ * Here's a list of default keys:
+ *
+ * {
+ * 8 : 'backspace',
+ * 9 : 'tab',
+ * 13 : 'enter!',
+ * 27 : 'escape!',
+ * 37 : 'left',
+ * 38 : 'up!',
+ * 39 : 'right',
+ * 40 : 'down!',
+ * 46 : 'delete',
+ * 108 : 'numpadEnter'
+ * }
+ *
+ * Please note the `!` at the end of some keys. This tells the core that by default
+ * this keypress will be trapped and not passed on to the text input.
+ *
+ * @name keys
+ * @default { ... }
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.options.keys
+ */
+ OPT_KEYS = 'keys',
+
+ /**
+ * The core triggers or reacts to the following events.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExt.events
+ */
+
+ /**
+ * Core triggers `preInvalidate` event before the dimensions of padding on the text input
+ * are set.
+ *
+ * @name preInvalidate
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.events.preInvalidate
+ */
+ EVENT_PRE_INVALIDATE = 'preInvalidate',
+
+ /**
+ * Core triggers `postInvalidate` event after the dimensions of padding on the text input
+ * are set.
+ *
+ * @name postInvalidate
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.events.postInvalidate
+ */
+ EVENT_POST_INVALIDATE = 'postInvalidate',
+
+ /**
+ * Core triggers `getFormData` on every key press to collect data that will be populated
+ * into the hidden input that will be submitted with the HTML form and data that will
+ * be displayed in the input field that user is currently interacting with.
+ *
+ * All plugins that wish to affect how the data is presented or sent must react to
+ * `getFormData` and populate the data in the following format:
+ *
+ * {
+ * input : {String},
+ * form : {Object}
+ * }
+ *
+ * The data key must be a numeric weight which will be used to determine which data
+ * ends up being used. Data with the highest numerical weight gets the priority. This
+ * allows plugins to set the final data regardless of their initialization order, which
+ * otherwise would be impossible.
+ *
+ * For example, the Tags and Autocomplete plugins have to work side by side and Tags
+ * plugin must get priority on setting the data. Therefore the Tags plugin sets data
+ * with the weight 200 where as the Autocomplete plugin sets data with the weight 100.
+ *
+ * Here's an example of a typical `getFormData` handler:
+ *
+ * TextExtPlugin.prototype.onGetFormData = function(e, data, keyCode)
+ * {
+ * data[100] = self.formDataObject('input value', 'form value');
+ * };
+ *
+ * Core also reacts to the `getFormData` and updates hidden input with data which will be
+ * submitted with the HTML form.
+ *
+ * @name getFormData
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.events.getFormData
+ */
+ EVENT_GET_FORM_DATA = 'getFormData',
+
+ /**
+ * Core triggers and reacts to the `setFormData` event to update the actual value in the
+ * hidden input that will be submitted with the HTML form. Second argument can be value
+ * of any type and by default it will be JSON serialized with `TextExt.serializeData()`
+ * function.
+ *
+ * @name setFormData
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExt.events.setFormData
+ */
+ EVENT_SET_FORM_DATA = 'setFormData',
+
+ /**
+ * Core triggers and reacts to the `setInputData` event to update the actual value in the
+ * text input that user is interacting with. Second argument must be of a `String` type
+ * the value of which will be set into the text input.
+ *
+ * @name setInputData
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExt.events.setInputData
+ */
+ EVENT_SET_INPUT_DATA = 'setInputData',
+
+ /**
+ * Core triggers `postInit` event to let plugins run code after all plugins have been
+ * created and initialized. This is a good place to set some kind of global values before
+ * somebody gets to use them. This is not the right place to expect all plugins to finish
+ * their initialization.
+ *
+ * @name postInit
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.events.postInit
+ */
+ EVENT_POST_INIT = 'postInit',
+
+ /**
+ * Core triggers `ready` event after all global configuration and prepearation has been
+ * done and the TextExt component is ready for use. Event handlers should expect all
+ * values to be set and the plugins to be in the final state.
+ *
+ * @name ready
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.events.ready
+ */
+ EVENT_READY = 'ready',
+
+ /**
+ * Core triggers `anyKeyUp` event for every key up event triggered within the component.
+ *
+ * @name anyKeyUp
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.events.anyKeyUp
+ */
+
+ /**
+ * Core triggers `anyKeyDown` event for every key down event triggered within the component.
+ *
+ * @name anyKeyDown
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.events.anyKeyDown
+ */
+
+ /**
+ * Core triggers `[name]KeyUp` event for every key specifid in the `keys` option that is
+ * triggered within the component.
+ *
+ * @name [name]KeyUp
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.events.[name]KeyUp
+ */
+
+ /**
+ * Core triggers `[name]KeyDown` event for every key specified in the `keys` option that is
+ * triggered within the component.
+ *
+ * @name [name]KeyDown
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.events.[name]KeyDown
+ */
+
+ /**
+ * Core triggers `[name]KeyPress` event for every key specified in the `keys` option that is
+ * triggered within the component.
+ *
+ * @name [name]KeyPress
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.events.[name]KeyPress
+ */
+
+ DEFAULT_OPTS = {
+ itemManager : ItemManager,
+
+ plugins : [],
+ ext : {},
+
+ html : {
+ wrap : '',
+ hidden : ' '
+ },
+
+ keys : {
+ 8 : 'backspace',
+ 9 : 'tab',
+ 13 : 'enter!',
+ 27 : 'escape!',
+ 37 : 'left',
+ 38 : 'up!',
+ 39 : 'right',
+ 40 : 'down!',
+ 46 : 'delete',
+ 108 : 'numpadEnter'
+ }
+ }
+ ;
+
+ // Freak out if there's no JSON.stringify function found
+ if(!stringify)
+ throw new Error('JSON.stringify() not found');
+
+ /**
+ * Returns object property by name where name is dot-separated and object is multiple levels deep.
+ * @param target Object Source object.
+ * @param name String Dot separated property name, ie `foo.bar.world`
+ * @id core.getProperty
+ */
+ function getProperty(source, name)
+ {
+ if(typeof(name) === 'string')
+ name = name.split('.');
+
+ var fullCamelCaseName = name.join('.').replace(/\.(\w)/g, function(match, letter) { return letter.toUpperCase() }),
+ nestedName = name.shift(),
+ result
+ ;
+
+ if(typeof(result = source[fullCamelCaseName]) != UNDEFINED)
+ result = result;
+
+ else if(typeof(result = source[nestedName]) != UNDEFINED && name.length > 0)
+ result = getProperty(result, name);
+
+ // name.length here should be zero
+ return result;
+ };
+
+ /**
+ * Hooks up specified events in the scope of the current object.
+ * @author agorbatchev
+ * @date 2011/08/09
+ */
+ function hookupEvents()
+ {
+ var args = slice.apply(arguments),
+ self = this,
+ target = args.length === 1 ? self : args.shift(),
+ event
+ ;
+
+ args = args[0] || {};
+
+ function bind(event, handler)
+ {
+ target.bind(event, function()
+ {
+ // apply handler to our PLUGIN object, not the target
+ return handler.apply(self, arguments);
+ });
+ }
+
+ for(event in args)
+ bind(event, args[event]);
+ };
+
+ function formDataObject(input, form)
+ {
+ return { 'input' : input, 'form' : form };
+ };
+
+ //--------------------------------------------------------------------------------
+ // ItemManager core component
+
+ p = ItemManager.prototype;
+
+ /**
+ * Initialization method called by the core during instantiation.
+ *
+ * @signature ItemManager.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id ItemManager.init
+ */
+ p.init = function(core)
+ {
+ };
+
+ /**
+ * Filters out items from the list that don't match the query and returns remaining items. Default
+ * implementation checks if the item starts with the query.
+ *
+ * @signature ItemManager.filter(list, query)
+ *
+ * @param list {Array} List of items. Default implementation works with strings.
+ * @param query {String} Query string.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id ItemManager.filter
+ */
+ p.filter = function(list, query)
+ {
+ var result = [],
+ i, item
+ ;
+
+ for(i = 0; i < list.length; i++)
+ {
+ item = list[i];
+ if(this.itemContains(item, query))
+ result.push(item);
+ }
+
+ return result;
+ };
+
+ /**
+ * Returns `true` if specified item contains another string, `false` otherwise. In the default implementation
+ * `String.indexOf()` is used to check if item string begins with the needle string.
+ *
+ * @signature ItemManager.itemContains(item, needle)
+ *
+ * @param item {Object} Item to check. Default implementation works with strings.
+ * @param needle {String} Search string to be found within the item.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id ItemManager.itemContains
+ */
+ p.itemContains = function(item, needle)
+ {
+ return this.itemToString(item).toLowerCase().indexOf(needle.toLowerCase()) == 0;
+ };
+
+ /**
+ * Converts specified string to item. Because default implemenation works with string, input string
+ * is simply returned back. To use custom objects, different implementation of this method could
+ * return something like `{ name : {String} }`.
+ *
+ * @signature ItemManager.stringToItem(str)
+ *
+ * @param str {String} Input string.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id ItemManager.stringToItem
+ */
+ p.stringToItem = function(str)
+ {
+ return str;
+ };
+
+ /**
+ * Converts specified item to string. Because default implemenation works with string, input string
+ * is simply returned back. To use custom objects, different implementation of this method could
+ * for example return `name` field of `{ name : {String} }`.
+ *
+ * @signature ItemManager.itemToString(item)
+ *
+ * @param item {Object} Input item to be converted to string.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id ItemManager.itemToString
+ */
+ p.itemToString = function(item)
+ {
+ return item;
+ };
+
+ /**
+ * Returns `true` if both items are equal, `false` otherwise. Because default implemenation works with
+ * string, input items are compared as strings. To use custom objects, different implementation of this
+ * method could for example compare `name` fields of `{ name : {String} }` type object.
+ *
+ * @signature ItemManager.compareItems(item1, item2)
+ *
+ * @param item1 {Object} First item.
+ * @param item2 {Object} Second item.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id ItemManager.compareItems
+ */
+ p.compareItems = function(item1, item2)
+ {
+ return item1 == item2;
+ };
+
+ //--------------------------------------------------------------------------------
+ // TextExt core component
+
+ p = TextExt.prototype;
+
+ /**
+ * Initializes current component instance with work with the supplied text input and options.
+ *
+ * @signature TextExt.init(input, opts)
+ *
+ * @param input {HTMLElement} Text input.
+ * @param opts {Object} Options.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.init
+ */
+ p.init = function(input, opts)
+ {
+ var self = this,
+ hiddenInput,
+ itemManager,
+ container
+ ;
+
+ self._defaults = $.extend({}, DEFAULT_OPTS);
+ self._opts = opts || {};
+ self._plugins = {};
+ self._itemManager = itemManager = new (self.opts(OPT_ITEM_MANAGER))();
+ input = $(input);
+ container = $(self.opts(OPT_HTML_WRAP));
+ hiddenInput = $(self.opts(OPT_HTML_HIDDEN));
+
+ input
+ .wrap(container)
+ .keydown(function(e) { return self.onKeyDown(e) })
+ .keyup(function(e) { return self.onKeyUp(e) })
+ .data('textext', self)
+ ;
+
+ // keep references to html elements using jQuery.data() to avoid circular references
+ $(self).data({
+ 'hiddenInput' : hiddenInput,
+ 'wrapElement' : input.parents('.text-wrap').first(),
+ 'input' : input
+ });
+
+ // set the name of the hidden input to the text input's name
+ hiddenInput.attr('name', input.attr('name'));
+ // remove name attribute from the text input
+ input.attr('name', null);
+ // add hidden input to the DOM
+ hiddenInput.insertAfter(input);
+
+ $.extend(true, itemManager, self.opts(OPT_EXT + '.item.manager'));
+ $.extend(true, self, self.opts(OPT_EXT + '.*'), self.opts(OPT_EXT + '.core'));
+
+ self.originalWidth = input.outerWidth();
+
+ self.invalidateBounds();
+
+ itemManager.init(self);
+
+ self.initPatches();
+ self.initPlugins(self.opts(OPT_PLUGINS), $.fn.textext.plugins);
+
+ self.on({
+ setFormData : self.onSetFormData,
+ getFormData : self.onGetFormData,
+ setInputData : self.onSetInputData,
+ anyKeyUp : self.onAnyKeyUp
+ });
+
+ self.trigger(EVENT_POST_INIT);
+ self.trigger(EVENT_READY);
+
+ self.getFormData(0);
+ };
+
+ /**
+ * Initialized all installed patches against current instance. The patches are initialized based on their
+ * initialization priority which is returned by each patch's `initPriority()` method. Priority
+ * is a `Number` where patches with higher value gets their `init()` method called before patches
+ * with lower priority value.
+ *
+ * This facilitates initializing of patches in certain order to insure proper dependencies
+ * regardless of which order they are loaded.
+ *
+ * By default all patches have the same priority - zero, which means they will be initialized
+ * in rorder they are loaded, that is unless `initPriority()` is overriden.
+ *
+ * @signature TextExt.initPatches()
+ *
+ * @author agorbatchev
+ * @date 2011/10/11
+ * @id TextExt.initPatches
+ */
+ p.initPatches = function()
+ {
+ var list = [],
+ source = $.fn.textext.patches,
+ name
+ ;
+
+ for(name in source)
+ list.push(name);
+
+ this.initPlugins(list, source);
+ };
+
+ /**
+ * Creates and initializes all specified plugins. The plugins are initialized based on their
+ * initialization priority which is returned by each plugin's `initPriority()` method. Priority
+ * is a `Number` where plugins with higher value gets their `init()` method called before plugins
+ * with lower priority value.
+ *
+ * This facilitates initializing of plugins in certain order to insure proper dependencies
+ * regardless of which order user enters them in the `plugins` option field.
+ *
+ * By default all plugins have the same priority - zero, which means they will be initialized
+ * in the same order as entered by the user.
+ *
+ * @signature TextExt.initPlugins(plugins)
+ *
+ * @param plugins {Array} List of plugin names to initialize.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.initPlugins
+ */
+ p.initPlugins = function(plugins, source)
+ {
+ var self = this,
+ ext, name, plugin, initList = [], i
+ ;
+
+ if(typeof(plugins) == 'string')
+ plugins = plugins.split(/\s*,\s*|\s+/g);
+
+ for(i = 0; i < plugins.length; i++)
+ {
+ name = plugins[i];
+ plugin = source[name];
+
+ if(plugin)
+ {
+ self._plugins[name] = plugin = new plugin();
+ self[name] = (function(plugin) {
+ return function(){ return plugin; }
+ })(plugin);
+ initList.push(plugin);
+ $.extend(true, plugin, self.opts(OPT_EXT + '.*'), self.opts(OPT_EXT + '.' + name));
+ }
+ }
+
+ // sort plugins based on their priority values
+ initList.sort(function(p1, p2)
+ {
+ p1 = p1.initPriority();
+ p2 = p2.initPriority();
+
+ return p1 === p2
+ ? 0
+ : p1 < p2 ? 1 : -1
+ ;
+ });
+
+ for(i = 0; i < initList.length; i++)
+ initList[i].init(self);
+ };
+
+ /**
+ * Returns true if specified plugin is was instantiated for the current instance of core.
+ *
+ * @signature TextExt.hasPlugin(name)
+ *
+ * @param name {String} Name of the plugin to check.
+ *
+ * @author agorbatchev
+ * @date 2011/12/28
+ * @id TextExt.hasPlugin
+ * @version 1.1
+ */
+ p.hasPlugin = function(name)
+ {
+ return !!this._plugins[name];
+ };
+
+ /**
+ * Allows to add multiple event handlers which will be execued in the scope of the current object.
+ *
+ * @signature TextExt.on([target], handlers)
+ *
+ * @param target {Object} **Optional**. Target object which has traditional `bind(event, handler)` method.
+ * Handler function will still be executed in the current object's scope.
+ * @param handlers {Object} Key/value pairs of event names and handlers, eg `{ event: handler }`.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.on
+ */
+ p.on = hookupEvents;
+
+ /**
+ * Binds an event handler to the input box that user interacts with.
+ *
+ * @signature TextExt.bind(event, handler)
+ *
+ * @param event {String} Event name.
+ * @param handler {Function} Event handler.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.bind
+ */
+ p.bind = function(event, handler)
+ {
+ this.input().bind(event, handler);
+ };
+
+ /**
+ * Triggers an event on the input box that user interacts with. All core events are originated here.
+ *
+ * @signature TextExt.trigger(event, ...args)
+ *
+ * @param event {String} Name of the event to trigger.
+ * @param ...args All remaining arguments will be passed to the event handler.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.trigger
+ */
+ p.trigger = function()
+ {
+ var args = arguments;
+ this.input().trigger(args[0], slice.call(args, 1));
+ };
+
+ /**
+ * Returns instance of `itemManager` that is used by the component.
+ *
+ * @signature TextExt.itemManager()
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.itemManager
+ */
+ p.itemManager = function()
+ {
+ return this._itemManager;
+ };
+
+ /**
+ * Returns jQuery input element with which user is interacting with.
+ *
+ * @signature TextExt.input()
+ *
+ * @author agorbatchev
+ * @date 2011/08/10
+ * @id TextExt.input
+ */
+ p.input = function()
+ {
+ return $(this).data('input');
+ };
+
+ /**
+ * Returns option value for the specified option by name. If the value isn't found in the user
+ * provided options, it will try looking for default value.
+ *
+ * @signature TextExt.opts(name)
+ *
+ * @param name {String} Option name as described in the options.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.opts
+ */
+ p.opts = function(name)
+ {
+ var result = getProperty(this._opts, name);
+ return typeof(result) == 'undefined' ? getProperty(this._defaults, name) : result;
+ };
+
+ /**
+ * Returns HTML element that was created from the `html.wrap` option. This is the top level HTML
+ * container for the text input with which user is interacting with.
+ *
+ * @signature TextExt.wrapElement()
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.wrapElement
+ */
+ p.wrapElement = function()
+ {
+ return $(this).data('wrapElement');
+ };
+
+ /**
+ * Updates container to match dimensions of the text input. Triggers `preInvalidate` and `postInvalidate`
+ * events.
+ *
+ * @signature TextExt.invalidateBounds()
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.invalidateBounds
+ */
+ p.invalidateBounds = function()
+ {
+ var self = this,
+ input = self.input(),
+ wrap = self.wrapElement(),
+ container = wrap.parent(),
+ width = self.originalWidth + 'px',
+ height
+ ;
+
+ self.trigger(EVENT_PRE_INVALIDATE);
+
+ height = input.outerHeight() + 'px';
+
+ // using css() method instead of width() and height() here because they don't seem to do the right thing in jQuery 1.8.x
+ // https://github.com/alexgorbatchev/jquery-textext/issues/74
+ input.css({ 'width' : width });
+ wrap.css({ 'width' : width, 'height' : height });
+ container.css({ 'height' : height });
+
+ self.trigger(EVENT_POST_INVALIDATE);
+ };
+
+ /**
+ * Focuses user input on the text box.
+ *
+ * @signature TextExt.focusInput()
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.focusInput
+ */
+ p.focusInput = function()
+ {
+ this.input()[0].focus();
+ };
+
+ /**
+ * Serializes data for to be set into the hidden input field and which will be submitted
+ * with the HTML form.
+ *
+ * By default simple JSON serialization is used. It's expected that `JSON.stringify`
+ * method would be available either through built in class in most modern browsers
+ * or through JSON2 library.
+ *
+ * @signature TextExt.serializeData(data)
+ *
+ * @param data {Object} Data to serialize.
+ *
+ * @author agorbatchev
+ * @date 2011/08/09
+ * @id TextExt.serializeData
+ */
+ p.serializeData = stringify;
+
+ /**
+ * Returns the hidden input HTML element which will be submitted with the HTML form.
+ *
+ * @signature TextExt.hiddenInput()
+ *
+ * @author agorbatchev
+ * @date 2011/08/09
+ * @id TextExt.hiddenInput
+ */
+ p.hiddenInput = function(value)
+ {
+ return $(this).data('hiddenInput');
+ };
+
+ /**
+ * Abstracted functionality to trigger an event and get the data with maximum weight set by all
+ * the event handlers. This functionality is used for the `getFormData` event.
+ *
+ * @signature TextExt.getWeightedEventResponse(event, args)
+ *
+ * @param event {String} Event name.
+ * @param args {Object} Argument to be passed with the event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExt.getWeightedEventResponse
+ */
+ p.getWeightedEventResponse = function(event, args)
+ {
+ var self = this,
+ data = {},
+ maxWeight = 0
+ ;
+
+ self.trigger(event, data, args);
+
+ for(var weight in data)
+ maxWeight = Math.max(maxWeight, weight);
+
+ return data[maxWeight];
+ };
+
+ /**
+ * Triggers the `getFormData` event to get all the plugins to return their data.
+ *
+ * After the data is returned, triggers `setFormData` and `setInputData` to update appopriate values.
+ *
+ * @signature TextExt.getFormData(keyCode)
+ *
+ * @param keyCode {Number} Key code number which has triggered this update. It's impotant to pass
+ * this value to the plugins because they might return different values based on the key that was
+ * pressed. For example, the Tags plugin returns an empty string for the `input` value if the enter
+ * key was pressed, otherwise it returns whatever is currently in the text input.
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExt.getFormData
+ */
+ p.getFormData = function(keyCode)
+ {
+ var self = this,
+ data = self.getWeightedEventResponse(EVENT_GET_FORM_DATA, keyCode || 0)
+ ;
+
+ self.trigger(EVENT_SET_FORM_DATA , data['form']);
+ self.trigger(EVENT_SET_INPUT_DATA , data['input']);
+ };
+
+ //--------------------------------------------------------------------------------
+ // Event handlers
+
+ /**
+ * Reacts to the `anyKeyUp` event and triggers the `getFormData` to change data that will be submitted
+ * with the form. Default behaviour is that everything that is typed in will be JSON serialized, so
+ * the end result will be a JSON string.
+ *
+ * @signature TextExt.onAnyKeyUp(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.onAnyKeyUp
+ */
+ p.onAnyKeyUp = function(e, keyCode)
+ {
+ this.getFormData(keyCode);
+ };
+
+ /**
+ * Reacts to the `setInputData` event and populates the input text field that user is currently
+ * interacting with.
+ *
+ * @signature TextExt.onSetInputData(e, data)
+ *
+ * @param e {Event} jQuery event.
+ * @param data {String} Value to be set.
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExt.onSetInputData
+ */
+ p.onSetInputData = function(e, data)
+ {
+ this.input().val(data);
+ };
+
+ /**
+ * Reacts to the `setFormData` event and populates the hidden input with will be submitted with
+ * the HTML form. The value will be serialized with `serializeData()` method.
+ *
+ * @signature TextExt.onSetFormData(e, data)
+ *
+ * @param e {Event} jQuery event.
+ * @param data {Object} Data that will be set.
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExt.onSetFormData
+ */
+ p.onSetFormData = function(e, data)
+ {
+ var self = this;
+ self.hiddenInput().val(self.serializeData(data));
+ };
+
+ /**
+ * Reacts to `getFormData` event triggered by the core. At the bare minimum the core will tell
+ * itself to use the current value in the text input as the data to be submitted with the HTML
+ * form.
+ *
+ * @signature TextExt.onGetFormData(e, data)
+ *
+ * @param e {Event} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/09
+ * @id TextExt.onGetFormData
+ */
+ p.onGetFormData = function(e, data)
+ {
+ var val = this.input().val();
+ data[0] = formDataObject(val, val);
+ };
+
+ //--------------------------------------------------------------------------------
+ // User mouse/keyboard input
+
+ /**
+ * Triggers `[name]KeyUp` and `[name]KeyPress` for every keystroke as described in the events.
+ *
+ * @signature TextExt.onKeyUp(e)
+ *
+ * @param e {Object} jQuery event.
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.onKeyUp
+ */
+
+ /**
+ * Triggers `[name]KeyDown` for every keystroke as described in the events.
+ *
+ * @signature TextExt.onKeyDown(e)
+ *
+ * @param e {Object} jQuery event.
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.onKeyDown
+ */
+
+ $(['Down', 'Up']).each(function()
+ {
+ var type = this.toString();
+
+ p['onKey' + type] = function(e)
+ {
+ var self = this,
+ keyName = self.opts(OPT_KEYS)[e.keyCode],
+ defaultResult = true
+ ;
+
+ if(keyName)
+ {
+ defaultResult = keyName.substr(-1) != '!';
+ keyName = keyName.replace('!', '');
+
+ self.trigger(keyName + 'Key' + type);
+
+ // manual *KeyPress event fimplementation for the function keys like Enter, Backspace, etc.
+ if(type == 'Up' && self._lastKeyDown == e.keyCode)
+ {
+ self._lastKeyDown = null;
+ self.trigger(keyName + 'KeyPress');
+ }
+
+ if(type == 'Down')
+ self._lastKeyDown = e.keyCode;
+ }
+
+ self.trigger('anyKey' + type, e.keyCode);
+
+ return defaultResult;
+ };
+ });
+
+ //--------------------------------------------------------------------------------
+ // Plugin Base
+
+ p = TextExtPlugin.prototype;
+
+ /**
+ * Allows to add multiple event handlers which will be execued in the scope of the current object.
+ *
+ * @signature TextExt.on([target], handlers)
+ *
+ * @param target {Object} **Optional**. Target object which has traditional `bind(event, handler)` method.
+ * Handler function will still be executed in the current object's scope.
+ * @param handlers {Object} Key/value pairs of event names and handlers, eg `{ event: handler }`.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtPlugin.on
+ */
+ p.on = hookupEvents;
+
+ /**
+ * Returns the hash object that `getFormData` triggered by the core expects.
+ *
+ * @signature TextExtPlugin.formDataObject(input, form)
+ *
+ * @param input {String} Value that will go into the text input that user is interacting with.
+ * @param form {Object} Value that will be serialized and put into the hidden that will be submitted
+ * with the HTML form.
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExtPlugin.formDataObject
+ */
+ p.formDataObject = formDataObject;
+
+ /**
+ * Initialization method called by the core during plugin instantiation. This method must be implemented
+ * by each plugin individually.
+ *
+ * @signature TextExtPlugin.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtPlugin.init
+ */
+ p.init = function(core) { throw new Error('Not implemented') };
+
+ /**
+ * Initialization method wich should be called by the plugin during the `init()` call.
+ *
+ * @signature TextExtPlugin.baseInit(core, defaults)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ * @param defaults {Object} Default plugin options. These will be checked if desired value wasn't
+ * found in the options supplied by the user.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtPlugin.baseInit
+ */
+ p.baseInit = function(core, defaults)
+ {
+ var self = this;
+
+ core._defaults = $.extend(true, core._defaults, defaults);
+ self._core = core;
+ self._timers = {};
+ };
+
+ /**
+ * Allows starting of multiple timeout calls. Each time this method is called with the same
+ * timer name, the timer is reset. This functionality is useful in cases where an action needs
+ * to occur only after a certain period of inactivity. For example, making an AJAX call after
+ * user stoped typing for 1 second.
+ *
+ * @signature TextExtPlugin.startTimer(name, delay, callback)
+ *
+ * @param name {String} Timer name.
+ * @param delay {Number} Delay in seconds.
+ * @param callback {Function} Callback function.
+ *
+ * @author agorbatchev
+ * @date 2011/08/25
+ * @id TextExtPlugin.startTimer
+ */
+ p.startTimer = function(name, delay, callback)
+ {
+ var self = this;
+
+ self.stopTimer(name);
+
+ self._timers[name] = setTimeout(
+ function()
+ {
+ delete self._timers[name];
+ callback.apply(self);
+ },
+ delay * 1000
+ );
+ };
+
+ /**
+ * Stops the timer by name without resetting it.
+ *
+ * @signature TextExtPlugin.stopTimer(name)
+ *
+ * @param name {String} Timer name.
+ *
+ * @author agorbatchev
+ * @date 2011/08/25
+ * @id TextExtPlugin.stopTimer
+ */
+ p.stopTimer = function(name)
+ {
+ clearTimeout(this._timers[name]);
+ };
+
+ /**
+ * Returns instance of the `TextExt` to which current instance of the plugin is attached to.
+ *
+ * @signature TextExtPlugin.core()
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtPlugin.core
+ */
+ p.core = function()
+ {
+ return this._core;
+ };
+
+ /**
+ * Shortcut to the core's `opts()` method. Returns option value.
+ *
+ * @signature TextExtPlugin.opts(name)
+ *
+ * @param name {String} Option name as described in the options.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtPlugin.opts
+ */
+ p.opts = function(name)
+ {
+ return this.core().opts(name);
+ };
+
+ /**
+ * Shortcut to the core's `itemManager()` method. Returns instance of the `ItemManger` that is
+ * currently in use.
+ *
+ * @signature TextExtPlugin.itemManager()
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtPlugin.itemManager
+ */
+ p.itemManager = function()
+ {
+ return this.core().itemManager();
+ };
+
+ /**
+ * Shortcut to the core's `input()` method. Returns instance of the HTML element that represents
+ * current text input.
+ *
+ * @signature TextExtPlugin.input()
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtPlugin.input
+ */
+ p.input = function()
+ {
+ return this.core().input();
+ };
+
+ /**
+ * Shortcut to the commonly used `this.input().val()` call to get or set value of the text input.
+ *
+ * @signature TextExtPlugin.val(value)
+ *
+ * @param value {String} Optional value. If specified, the value will be set, otherwise it will be
+ * returned.
+ *
+ * @author agorbatchev
+ * @date 2011/08/20
+ * @id TextExtPlugin.val
+ */
+ p.val = function(value)
+ {
+ var input = this.input();
+
+ if(typeof(value) === UNDEFINED)
+ return input.val();
+ else
+ input.val(value);
+ };
+
+ /**
+ * Shortcut to the core's `trigger()` method. Triggers specified event with arguments on the
+ * component core.
+ *
+ * @signature TextExtPlugin.trigger(event, ...args)
+ *
+ * @param event {String} Name of the event to trigger.
+ * @param ...args All remaining arguments will be passed to the event handler.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtPlugin.trigger
+ */
+ p.trigger = function()
+ {
+ var core = this.core();
+ core.trigger.apply(core, arguments);
+ };
+
+ /**
+ * Shortcut to the core's `bind()` method. Binds specified handler to the event.
+ *
+ * @signature TextExtPlugin.bind(event, handler)
+ *
+ * @param event {String} Event name.
+ * @param handler {Function} Event handler.
+ *
+ * @author agorbatchev
+ * @date 2011/08/20
+ * @id TextExtPlugin.bind
+ */
+ p.bind = function(event, handler)
+ {
+ this.core().bind(event, handler);
+ };
+
+ /**
+ * Returns initialization priority for this plugin. If current plugin depends upon some other plugin
+ * to be initialized before or after, priority needs to be adjusted accordingly. Plugins with higher
+ * priority initialize before plugins with lower priority.
+ *
+ * Default initialization priority is `0`.
+ *
+ * @signature TextExtPlugin.initPriority()
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExtPlugin.initPriority
+ */
+ p.initPriority = function()
+ {
+ return 0;
+ };
+
+ //--------------------------------------------------------------------------------
+ // jQuery Integration
+
+ /**
+ * TextExt integrates as a jQuery plugin available through the `$(selector).textext(opts)` call. If
+ * `opts` argument is passed, then a new instance of `TextExt` will be created for all the inputs
+ * that match the `selector`. If `opts` wasn't passed and TextExt was already intantiated for
+ * inputs that match the `selector`, array of `TextExt` instances will be returned instead.
+ *
+ * // will create a new instance of `TextExt` for all elements that match `.sample`
+ * $('.sample').textext({ ... });
+ *
+ * // will return array of all `TextExt` instances
+ * var list = $('.sample').textext();
+ *
+ * The following properties are also exposed through the jQuery `$.fn.textext`:
+ *
+ * * `TextExt` -- `TextExt` class.
+ * * `TextExtPlugin` -- `TextExtPlugin` class.
+ * * `ItemManager` -- `ItemManager` class.
+ * * `plugins` -- Key/value table of all registered plugins.
+ * * `addPlugin(name, constructor)` -- All plugins should register themselves using this function.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExt.jquery
+ */
+
+ var cssInjected = false;
+
+ var textext = $.fn.textext = function(opts)
+ {
+ var css;
+
+ if(!cssInjected && (css = $.fn.textext.css) != null)
+ {
+ $('head').append('');
+ cssInjected = true;
+ }
+
+ return this.map(function()
+ {
+ var self = $(this);
+
+ if(opts == null)
+ return self.data('textext');
+
+ var instance = new TextExt();
+
+ instance.init(self, opts);
+ self.data('textext', instance);
+
+ return instance.input()[0];
+ });
+ };
+
+ /**
+ * This static function registers a new plugin which makes it available through the `plugins` option
+ * to the end user. The name specified here is the name the end user would put in the `plugins` option
+ * to add this plugin to a new instance of TextExt.
+ *
+ * @signature $.fn.textext.addPlugin(name, constructor)
+ *
+ * @param name {String} Name of the plugin.
+ * @param constructor {Function} Plugin constructor.
+ *
+ * @author agorbatchev
+ * @date 2011/10/11
+ * @id TextExt.addPlugin
+ */
+ textext.addPlugin = function(name, constructor)
+ {
+ textext.plugins[name] = constructor;
+ constructor.prototype = new textext.TextExtPlugin();
+ };
+
+ /**
+ * This static function registers a new patch which is added to each instance of TextExt. If you are
+ * adding a new patch, make sure to call this method.
+ *
+ * @signature $.fn.textext.addPatch(name, constructor)
+ *
+ * @param name {String} Name of the patch.
+ * @param constructor {Function} Patch constructor.
+ *
+ * @author agorbatchev
+ * @date 2011/10/11
+ * @id TextExt.addPatch
+ */
+ textext.addPatch = function(name, constructor)
+ {
+ textext.patches[name] = constructor;
+ constructor.prototype = new textext.TextExtPlugin();
+ };
+
+ textext.TextExt = TextExt;
+ textext.TextExtPlugin = TextExtPlugin;
+ textext.ItemManager = ItemManager;
+ textext.plugins = {};
+ textext.patches = {};
+})(jQuery);
+
+(function($)
+{
+ function TextExtIE9Patches() {};
+
+ $.fn.textext.TextExtIE9Patches = TextExtIE9Patches;
+ $.fn.textext.addPatch('ie9',TextExtIE9Patches);
+
+ var p = TextExtIE9Patches.prototype;
+
+ p.init = function(core)
+ {
+ if(navigator.userAgent.indexOf('MSIE 9') == -1)
+ return;
+
+ var self = this;
+
+ core.on({ postInvalidate : self.onPostInvalidate });
+ };
+
+ p.onPostInvalidate = function()
+ {
+ var self = this,
+ input = self.input(),
+ val = input.val()
+ ;
+
+ // agorbatchev :: IE9 doesn't seem to update the padding if box-sizing is on until the
+ // text box value changes, so forcing this change seems to do the trick of updating
+ // IE's padding visually.
+ input.val(Math.random());
+ input.val(val);
+ };
+})(jQuery);
+
diff --git a/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.ajax.js b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.ajax.js
new file mode 100644
index 0000000..073f46a
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.ajax.js
@@ -0,0 +1,354 @@
+/**
+ * jQuery TextExt Plugin
+ * http://textextjs.com
+ *
+ * @version 1.3.1
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
+ * @license MIT License
+ */
+(function($)
+{
+ /**
+ * AJAX plugin is very useful if you want to load list of items from a data point and pass it
+ * to the Autocomplete or Filter plugins.
+ *
+ * Because it meant to be as a helper method for either Autocomplete or Filter plugin, without
+ * either of these two present AJAX plugin won't do anything.
+ *
+ * @author agorbatchev
+ * @date 2011/08/16
+ * @id TextExtAjax
+ */
+ function TextExtAjax() {};
+
+ $.fn.textext.TextExtAjax = TextExtAjax;
+ $.fn.textext.addPlugin('ajax', TextExtAjax);
+
+ var p = TextExtAjax.prototype,
+
+ /**
+ * AJAX plugin options are grouped under `ajax` when passed to the `$().textext()` function. Be
+ * mindful that the whole `ajax` object is also passed to jQuery `$.ajax` call which means that
+ * you can change all jQuery options as well. Please refer to the jQuery documentation on how
+ * to set url and all other parameters. For example:
+ *
+ * $('textarea').textext({
+ * plugins: 'ajax',
+ * ajax: {
+ * url: 'http://...'
+ * }
+ * })
+ *
+ * **Important**: Because it's necessary to pass options to `jQuery.ajax()` in a single object,
+ * all jQuery related AJAX options like `url`, `dataType`, etc **must** be within the `ajax` object.
+ * This is the exception to general rule that TextExt options can be specified in dot or camel case
+ * notation.
+ *
+ * @author agorbatchev
+ * @date 2011/08/16
+ * @id TextExtAjax.options
+ */
+
+ /**
+ * By default, when user starts typing into the text input, AJAX plugin will start making requests
+ * to the `url` that you have specified and will pass whatever user has typed so far as a parameter
+ * named `q`, eg `?q=foo`.
+ *
+ * If you wish to change this behaviour, you can pass a function as a value for this option which
+ * takes one argument (the user input) and should return a key/value object that will be converted
+ * to the request parameters. For example:
+ *
+ * 'dataCallback' : function(query)
+ * {
+ * return { 'search' : query };
+ * }
+ *
+ * @name ajax.data.callback
+ * @default null
+ * @author agorbatchev
+ * @date 2011/08/16
+ * @id TextExtAjax.options.data.callback
+ */
+ OPT_DATA_CALLBACK = 'ajax.data.callback',
+
+ /**
+ * By default, the server end point is constantly being reloaded whenever user changes the value
+ * in the text input. If you'd rather have the client do result filtering, you can return all
+ * possible results from the server and cache them on the client by setting this option to `true`.
+ *
+ * In such a case, only one call to the server will be made and filtering will be performed on
+ * the client side using `ItemManager` attached to the core.
+ *
+ * @name ajax.data.results
+ * @default false
+ * @author agorbatchev
+ * @date 2011/08/16
+ * @id TextExtAjax.options.cache.results
+ */
+ OPT_CACHE_RESULTS = 'ajax.cache.results',
+
+ /**
+ * The loading message delay is set in seconds and will specify how long it would take before
+ * user sees the message. If you don't want user to ever see this message, set the option value
+ * to `Number.MAX_VALUE`.
+ *
+ * @name ajax.loading.delay
+ * @default 0.5
+ * @author agorbatchev
+ * @date 2011/08/16
+ * @id TextExtAjax.options.loading.delay
+ */
+ OPT_LOADING_DELAY = 'ajax.loading.delay',
+
+ /**
+ * Whenever an AJAX request is made and the server takes more than the number of seconds specified
+ * in `ajax.loading.delay` to respond, the message specified in this option will appear in the drop
+ * down.
+ *
+ * @name ajax.loading.message
+ * @default "Loading..."
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAjax.options.loading.message
+ */
+ OPT_LOADING_MESSAGE = 'ajax.loading.message',
+
+ /**
+ * When user is typing in or otherwise changing the value of the text input, it's undesirable to make
+ * an AJAX request for every keystroke. Instead it's more conservative to send a request every number
+ * of seconds while user is typing the value. This number of seconds is specified by the `ajax.type.delay`
+ * option.
+ *
+ * @name ajax.type.delay
+ * @default 0.5
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAjax.options.type.delay
+ */
+ OPT_TYPE_DELAY = 'ajax.type.delay',
+
+ /**
+ * AJAX plugin dispatches or reacts to the following events.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAjax.events
+ */
+
+ /**
+ * AJAX plugin reacts to the `getSuggestions` event dispatched by the Autocomplete plugin.
+ *
+ * @name getSuggestions
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAjax.events.getSuggestions
+ */
+
+ /**
+ * In the event of successful AJAX request, the AJAX coponent dispatches the `setSuggestions`
+ * event meant to be recieved by the Autocomplete plugin.
+ *
+ * @name setSuggestions
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAjax.events.setSuggestions
+ */
+ EVENT_SET_SUGGESTION = 'setSuggestions',
+
+ /**
+ * AJAX plugin dispatches the `showDropdown` event which Autocomplete plugin is expecting.
+ * This is used to temporarily show the loading message if the AJAX request is taking longer
+ * than expected.
+ *
+ * @name showDropdown
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAjax.events.showDropdown
+ */
+ EVENT_SHOW_DROPDOWN = 'showDropdown',
+
+ TIMER_LOADING = 'loading',
+
+ DEFAULT_OPTS = {
+ ajax : {
+ typeDelay : 0.5,
+ loadingMessage : 'Loading...',
+ loadingDelay : 0.5,
+ cacheResults : false,
+ dataCallback : null
+ }
+ }
+ ;
+
+ /**
+ * Initialization method called by the core during plugin instantiation.
+ *
+ * @signature TextExtAjax.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAjax.init
+ */
+ p.init = function(core)
+ {
+ var self = this;
+
+ self.baseInit(core, DEFAULT_OPTS);
+
+ self.on({
+ getSuggestions : self.onGetSuggestions
+ });
+
+ self._suggestions = null;
+ };
+
+ /**
+ * Performas an async AJAX with specified options.
+ *
+ * @signature TextExtAjax.load(query)
+ *
+ * @param query {String} Value that user has typed into the text area which is
+ * presumably the query.
+ *
+ * @author agorbatchev
+ * @date 2011/08/14
+ * @id TextExtAjax.load
+ */
+ p.load = function(query)
+ {
+ var self = this,
+ dataCallback = self.opts(OPT_DATA_CALLBACK) || function(query) { return { q : query } },
+ opts
+ ;
+
+ opts = $.extend(true,
+ {
+ data : dataCallback(query),
+ success : function(data) { self.onComplete(data, query) },
+ error : function(jqXHR, message) { console.error(message, query) }
+ },
+ self.opts('ajax')
+ );
+
+ $.ajax(opts);
+ };
+
+ /**
+ * Successful call AJAX handler. Takes the data that came back from AJAX and the
+ * original query that was used to make the call.
+ *
+ * @signature TextExtAjax.onComplete(data, query)
+ *
+ * @param data {Object} Data loaded from the server, should be an Array of strings
+ * by default or whatever data structure your custom `ItemManager` implements.
+ *
+ * @param query {String} Query string, ie whatever user has typed in.
+ *
+ * @author agorbatchev
+ * @date 2011/08/14
+ * @id TextExtAjax.onComplete
+ */
+ p.onComplete = function(data, query)
+ {
+ var self = this,
+ result = data
+ ;
+
+ self.dontShowLoading();
+
+ // If results are expected to be cached, then we store the original
+ // data set and return the filtered one based on the original query.
+ // That means we do filtering on the client side, instead of the
+ // server side.
+ if(self.opts(OPT_CACHE_RESULTS) == true)
+ {
+ self._suggestions = data;
+ result = self.itemManager().filter(data, query);
+ }
+
+ self.trigger(EVENT_SET_SUGGESTION, { result : result });
+ };
+
+ /**
+ * If show loading message timer was started, calling this function disables it,
+ * otherwise nothing else happens.
+ *
+ * @signature TextExtAjax.dontShowLoading()
+ *
+ * @author agorbatchev
+ * @date 2011/08/16
+ * @id TextExtAjax.dontShowLoading
+ */
+ p.dontShowLoading = function()
+ {
+ this.stopTimer(TIMER_LOADING);
+ };
+
+ /**
+ * Shows message specified in `ajax.loading.message` if loading data takes more than
+ * number of seconds specified in `ajax.loading.delay`.
+ *
+ * @signature TextExtAjax.showLoading()
+ *
+ * @author agorbatchev
+ * @date 2011/08/15
+ * @id TextExtAjax.showLoading
+ */
+ p.showLoading = function()
+ {
+ var self = this;
+
+ self.dontShowLoading();
+ self.startTimer(
+ TIMER_LOADING,
+ self.opts(OPT_LOADING_DELAY),
+ function()
+ {
+ self.trigger(EVENT_SHOW_DROPDOWN, function(autocomplete)
+ {
+ autocomplete.clearItems();
+ var node = autocomplete.addDropdownItem(self.opts(OPT_LOADING_MESSAGE));
+ node.addClass('text-loading');
+ });
+ }
+ );
+ };
+
+ /**
+ * Reacts to the `getSuggestions` event and begin loading suggestions. If
+ * `ajax.cache.results` is specified, all calls after the first one will use
+ * cached data and filter it with the `core.itemManager.filter()`.
+ *
+ * @signature TextExtAjax.onGetSuggestions(e, data)
+ *
+ * @param e {Object} jQuery event.
+ * @param data {Object} Data structure passed with the `getSuggestions` event
+ * which contains the user query, eg `{ query : "..." }`.
+ *
+ * @author agorbatchev
+ * @date 2011/08/15
+ * @id TextExtAjax.onGetSuggestions
+ */
+ p.onGetSuggestions = function(e, data)
+ {
+ var self = this,
+ suggestions = self._suggestions,
+ query = (data || {}).query || ''
+ ;
+
+ if(suggestions && self.opts(OPT_CACHE_RESULTS) === true)
+ return self.onComplete(suggestions, query);
+
+ self.startTimer(
+ 'ajax',
+ self.opts(OPT_TYPE_DELAY),
+ function()
+ {
+ self.showLoading();
+ self.load(query);
+ }
+ );
+ };
+})(jQuery);
diff --git a/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.arrow.js b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.arrow.js
new file mode 100644
index 0000000..0531acb
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.arrow.js
@@ -0,0 +1,106 @@
+/**
+ * jQuery TextExt Plugin
+ * http://textextjs.com
+ *
+ * @version 1.3.1
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
+ * @license MIT License
+ */
+(function($)
+{
+ /**
+ * Displays a dropdown style arrow button. The `TextExtArrow` works together with the
+ * `TextExtAutocomplete` plugin and whenever clicked tells the autocomplete plugin to
+ * display its suggestions.
+ *
+ * @author agorbatchev
+ * @date 2011/12/27
+ * @id TextExtArrow
+ */
+ function TextExtArrow() {};
+
+ $.fn.textext.TextExtArrow = TextExtArrow;
+ $.fn.textext.addPlugin('arrow', TextExtArrow);
+
+ var p = TextExtArrow.prototype,
+ /**
+ * Arrow plugin only has one option and that is its HTML template. It could be
+ * changed when passed to the `$().textext()` function. For example:
+ *
+ * $('textarea').textext({
+ * plugins: 'arrow',
+ * html: {
+ * arrow: " "
+ * }
+ * })
+ *
+ * @author agorbatchev
+ * @date 2011/12/27
+ * @id TextExtArrow.options
+ */
+
+ /**
+ * HTML source that is used to generate markup required for the arrow.
+ *
+ * @name html.arrow
+ * @default '
'
+ * @author agorbatchev
+ * @date 2011/12/27
+ * @id TextExtArrow.options.html.arrow
+ */
+ OPT_HTML_ARROW = 'html.arrow',
+
+ DEFAULT_OPTS = {
+ html : {
+ arrow : '
'
+ }
+ }
+ ;
+
+ /**
+ * Initialization method called by the core during plugin instantiation.
+ *
+ * @signature TextExtArrow.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/12/27
+ * @id TextExtArrow.init
+ */
+ p.init = function(core)
+ {
+ var self = this,
+ arrow
+ ;
+
+ self.baseInit(core, DEFAULT_OPTS);
+
+ self._arrow = arrow = $(self.opts(OPT_HTML_ARROW));
+ self.core().wrapElement().append(arrow);
+ arrow.bind('click', function(e) { self.onArrowClick(e); });
+ };
+
+ //--------------------------------------------------------------------------------
+ // Event handlers
+
+ /**
+ * Reacts to the `click` event whenever user clicks the arrow.
+ *
+ * @signature TextExtArrow.onArrowClick(e)
+ *
+ * @param e {Object} jQuery event.
+ * @author agorbatchev
+ * @date 2011/12/27
+ * @id TextExtArrow.onArrowClick
+ */
+ p.onArrowClick = function(e)
+ {
+ this.trigger('toggleDropdown');
+ this.core().focusInput();
+ };
+
+ //--------------------------------------------------------------------------------
+ // Core functionality
+
+})(jQuery);
diff --git a/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.autocomplete.js b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.autocomplete.js
new file mode 100644
index 0000000..fd493e4
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.autocomplete.js
@@ -0,0 +1,1110 @@
+/**
+ * jQuery TextExt Plugin
+ * http://textextjs.com
+ *
+ * @version 1.3.1
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
+ * @license MIT License
+ */
+(function($)
+{
+ /**
+ * Autocomplete plugin brings the classic autocomplete functionality to the TextExt ecosystem.
+ * The gist of functionality is when user starts typing in, for example a term or a tag, a
+ * dropdown would be presented with possible suggestions to complete the input quicker.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete
+ */
+ function TextExtAutocomplete() {};
+
+ $.fn.textext.TextExtAutocomplete = TextExtAutocomplete;
+ $.fn.textext.addPlugin('autocomplete', TextExtAutocomplete);
+
+ var p = TextExtAutocomplete.prototype,
+
+ CSS_DOT = '.',
+ CSS_SELECTED = 'text-selected',
+ CSS_DOT_SELECTED = CSS_DOT + CSS_SELECTED,
+ CSS_SUGGESTION = 'text-suggestion',
+ CSS_DOT_SUGGESTION = CSS_DOT + CSS_SUGGESTION,
+ CSS_LABEL = 'text-label',
+ CSS_DOT_LABEL = CSS_DOT + CSS_LABEL,
+
+ /**
+ * Autocomplete plugin options are grouped under `autocomplete` when passed to the
+ * `$().textext()` function. For example:
+ *
+ * $('textarea').textext({
+ * plugins: 'autocomplete',
+ * autocomplete: {
+ * dropdownPosition: 'above'
+ * }
+ * })
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.options
+ */
+
+ /**
+ * This is a toggle switch to enable or disable the Autucomplete plugin. The value is checked
+ * each time at the top level which allows you to toggle this setting on the fly.
+ *
+ * @name autocomplete.enabled
+ * @default true
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.options.autocomplete.enabled
+ */
+ OPT_ENABLED = 'autocomplete.enabled',
+
+ /**
+ * This option allows to specify position of the dropdown. The two possible values
+ * are `above` and `below`.
+ *
+ * @name autocomplete.dropdown.position
+ * @default "below"
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.options.autocomplete.dropdown.position
+ */
+ OPT_POSITION = 'autocomplete.dropdown.position',
+
+ /**
+ * This option allows to specify maximum height of the dropdown. Value is taken directly, so
+ * if desired height is 200 pixels, value must be `200px`.
+ *
+ * @name autocomplete.dropdown.maxHeight
+ * @default "100px"
+ * @author agorbatchev
+ * @date 2011/12/29
+ * @id TextExtAutocomplete.options.autocomplete.dropdown.maxHeight
+ * @version 1.1
+ */
+ OPT_MAX_HEIGHT = 'autocomplete.dropdown.maxHeight',
+
+ /**
+ * This option allows to override how a suggestion item is rendered. The value should be
+ * a function, the first argument of which is suggestion to be rendered and `this` context
+ * is the current instance of `TextExtAutocomplete`.
+ *
+ * [Click here](/manual/examples/autocomplete-with-custom-render.html) to see a demo.
+ *
+ * For example:
+ *
+ * $('textarea').textext({
+ * plugins: 'autocomplete',
+ * autocomplete: {
+ * render: function(suggestion)
+ * {
+ * return '' + suggestion + ' ';
+ * }
+ * }
+ * })
+ *
+ * @name autocomplete.render
+ * @default null
+ * @author agorbatchev
+ * @date 2011/12/23
+ * @id TextExtAutocomplete.options.autocomplete.render
+ * @version 1.1
+ */
+ OPT_RENDER = 'autocomplete.render',
+
+ /**
+ * HTML source that is used to generate the dropdown.
+ *
+ * @name html.dropdown
+ * @default ''
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.options.html.dropdown
+ */
+ OPT_HTML_DROPDOWN = 'html.dropdown',
+
+ /**
+ * HTML source that is used to generate each suggestion.
+ *
+ * @name html.suggestion
+ * @default '
'
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.options.html.suggestion
+ */
+ OPT_HTML_SUGGESTION = 'html.suggestion',
+
+ /**
+ * Autocomplete plugin triggers or reacts to the following events.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.events
+ */
+
+ /**
+ * Autocomplete plugin triggers and reacts to the `hideDropdown` to hide the dropdown if it's
+ * already visible.
+ *
+ * @name hideDropdown
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.events.hideDropdown
+ */
+ EVENT_HIDE_DROPDOWN = 'hideDropdown',
+
+ /**
+ * Autocomplete plugin triggers and reacts to the `showDropdown` to show the dropdown if it's
+ * not already visible.
+ *
+ * It's possible to pass a render callback function which will be called instead of the
+ * default `TextExtAutocomplete.renderSuggestions()`.
+ *
+ * Here's how another plugin should trigger this event with the optional render callback:
+ *
+ * this.trigger('showDropdown', function(autocomplete)
+ * {
+ * autocomplete.clearItems();
+ * var node = autocomplete.addDropdownItem('Item ');
+ * node.addClass('new-look');
+ * });
+ *
+ * @name showDropdown
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.events.showDropdown
+ */
+ EVENT_SHOW_DROPDOWN = 'showDropdown',
+
+ /**
+ * Autocomplete plugin reacts to the `setSuggestions` event triggered by other plugins which
+ * wish to populate the suggestion items. Suggestions should be passed as event argument in the
+ * following format: `{ data : [ ... ] }`.
+ *
+ * Here's how another plugin should trigger this event:
+ *
+ * this.trigger('setSuggestions', { data : [ "item1", "item2" ] });
+ *
+ * @name setSuggestions
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.events.setSuggestions
+ */
+
+ /**
+ * Autocomplete plugin triggers the `getSuggestions` event and expects to get results by listening for
+ * the `setSuggestions` event.
+ *
+ * @name getSuggestions
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.events.getSuggestions
+ */
+ EVENT_GET_SUGGESTIONS = 'getSuggestions',
+
+ /**
+ * Autocomplete plugin triggers `getFormData` event with the current suggestion so that the the core
+ * will be updated with serialized data to be submitted with the HTML form.
+ *
+ * @name getFormData
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtAutocomplete.events.getFormData
+ */
+ EVENT_GET_FORM_DATA = 'getFormData',
+
+ /**
+ * Autocomplete plugin reacts to `toggleDropdown` event and either shows or hides the dropdown
+ * depending if it's currently hidden or visible.
+ *
+ * @name toggleDropdown
+ * @author agorbatchev
+ * @date 2011/12/27
+ * @id TextExtAutocomplete.events.toggleDropdown
+ * @version 1.1
+ */
+ EVENT_TOGGLE_DROPDOWN = 'toggleDropdown',
+
+ POSITION_ABOVE = 'above',
+ POSITION_BELOW = 'below',
+
+ DATA_MOUSEDOWN_ON_AUTOCOMPLETE = 'mousedownOnAutocomplete',
+
+ DEFAULT_OPTS = {
+ autocomplete : {
+ enabled : true,
+ dropdown : {
+ position : POSITION_BELOW,
+ maxHeight : '100px'
+ }
+ },
+
+ html : {
+ dropdown : '',
+ suggestion : '
'
+ }
+ }
+ ;
+
+ /**
+ * Initialization method called by the core during plugin instantiation.
+ *
+ * @signature TextExtAutocomplete.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.init
+ */
+ p.init = function(core)
+ {
+ var self = this;
+
+ self.baseInit(core, DEFAULT_OPTS);
+
+ var input = self.input(),
+ container
+ ;
+
+ if(self.opts(OPT_ENABLED) === true)
+ {
+ self.on({
+ blur : self.onBlur,
+ anyKeyUp : self.onAnyKeyUp,
+ deleteKeyUp : self.onAnyKeyUp,
+ backspaceKeyPress : self.onBackspaceKeyPress,
+ enterKeyPress : self.onEnterKeyPress,
+ escapeKeyPress : self.onEscapeKeyPress,
+ setSuggestions : self.onSetSuggestions,
+ showDropdown : self.onShowDropdown,
+ hideDropdown : self.onHideDropdown,
+ toggleDropdown : self.onToggleDropdown,
+ postInvalidate : self.positionDropdown,
+ getFormData : self.onGetFormData,
+
+ // using keyDown for up/down keys so that repeat events are
+ // captured and user can scroll up/down by holding the keys
+ downKeyDown : self.onDownKeyDown,
+ upKeyDown : self.onUpKeyDown
+ });
+
+ container = $(self.opts(OPT_HTML_DROPDOWN));
+ container.insertAfter(input);
+
+ self.on(container, {
+ mouseover : self.onMouseOver,
+ mousedown : self.onMouseDown,
+ click : self.onClick
+ });
+
+ container
+ .css('maxHeight', self.opts(OPT_MAX_HEIGHT))
+ .addClass('text-position-' + self.opts(OPT_POSITION))
+ ;
+
+ $(self).data('container', container);
+
+ $(document.body).click(function(e)
+ {
+ if (self.isDropdownVisible() && !self.withinWrapElement(e.target))
+ self.trigger(EVENT_HIDE_DROPDOWN);
+ });
+
+ self.positionDropdown();
+ }
+ };
+
+ /**
+ * Returns top level dropdown container HTML element.
+ *
+ * @signature TextExtAutocomplete.containerElement()
+ *
+ * @author agorbatchev
+ * @date 2011/08/15
+ * @id TextExtAutocomplete.containerElement
+ */
+ p.containerElement = function()
+ {
+ return $(this).data('container');
+ };
+
+ //--------------------------------------------------------------------------------
+ // User mouse/keyboard input
+
+ /**
+ * Reacts to the `mouseOver` event triggered by the TextExt core.
+ *
+ * @signature TextExtAutocomplete.onMouseOver(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onMouseOver
+ */
+ p.onMouseOver = function(e)
+ {
+ var self = this,
+ target = $(e.target)
+ ;
+
+ if(target.is(CSS_DOT_SUGGESTION))
+ {
+ self.clearSelected();
+ target.addClass(CSS_SELECTED);
+ }
+ };
+
+ /**
+ * Reacts to the `mouseDown` event triggered by the TextExt core.
+ *
+ * @signature TextExtAutocomplete.onMouseDown(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author adamayres
+ * @date 2012/01/13
+ * @id TextExtAutocomplete.onMouseDown
+ */
+ p.onMouseDown = function(e)
+ {
+ this.containerElement().data(DATA_MOUSEDOWN_ON_AUTOCOMPLETE, true);
+ };
+
+ /**
+ * Reacts to the `click` event triggered by the TextExt core.
+ *
+ * @signature TextExtAutocomplete.onClick(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onClick
+ */
+ p.onClick = function(e)
+ {
+ var self = this,
+ target = $(e.target)
+ ;
+
+ if(target.is(CSS_DOT_SUGGESTION) || target.is(CSS_DOT_LABEL))
+ self.trigger('enterKeyPress');
+
+ if (self.core().hasPlugin('tags'))
+ self.val('');
+ };
+
+ /**
+ * Reacts to the `blur` event triggered by the TextExt core.
+ *
+ * @signature TextExtAutocomplete.onBlur(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onBlur
+ */
+ p.onBlur = function(e)
+ {
+ var self = this,
+ container = self.containerElement(),
+ isBlurByMousedown = container.data(DATA_MOUSEDOWN_ON_AUTOCOMPLETE) === true
+ ;
+
+ // only trigger a close event if the blur event was
+ // not triggered by a mousedown event on the autocomplete
+ // otherwise set focus back back on the input
+ if(self.isDropdownVisible())
+ isBlurByMousedown ? self.core().focusInput() : self.trigger(EVENT_HIDE_DROPDOWN);
+
+ container.removeData(DATA_MOUSEDOWN_ON_AUTOCOMPLETE);
+ };
+
+ /**
+ * Reacts to the `backspaceKeyPress` event triggered by the TextExt core.
+ *
+ * @signature TextExtAutocomplete.onBackspaceKeyPress(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onBackspaceKeyPress
+ */
+ p.onBackspaceKeyPress = function(e)
+ {
+ var self = this,
+ isEmpty = self.val().length > 0
+ ;
+
+ if(isEmpty || self.isDropdownVisible())
+ self.getSuggestions();
+ };
+
+ /**
+ * Reacts to the `anyKeyUp` event triggered by the TextExt core.
+ *
+ * @signature TextExtAutocomplete.onAnyKeyUp(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onAnyKeyUp
+ */
+ p.onAnyKeyUp = function(e, keyCode)
+ {
+ var self = this,
+ isFunctionKey = self.opts('keys.' + keyCode) != null
+ ;
+
+ if(self.val().length > 0 && !isFunctionKey)
+ self.getSuggestions();
+ };
+
+ /**
+ * Reacts to the `downKeyDown` event triggered by the TextExt core.
+ *
+ * @signature TextExtAutocomplete.onDownKeyDown(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onDownKeyDown
+ */
+ p.onDownKeyDown = function(e)
+ {
+ var self = this;
+
+ self.isDropdownVisible()
+ ? self.toggleNextSuggestion()
+ : self.getSuggestions()
+ ;
+ };
+
+ /**
+ * Reacts to the `upKeyDown` event triggered by the TextExt core.
+ *
+ * @signature TextExtAutocomplete.onUpKeyDown(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onUpKeyDown
+ */
+ p.onUpKeyDown = function(e)
+ {
+ this.togglePreviousSuggestion();
+ };
+
+ /**
+ * Reacts to the `enterKeyPress` event triggered by the TextExt core.
+ *
+ * @signature TextExtAutocomplete.onEnterKeyPress(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onEnterKeyPress
+ */
+ p.onEnterKeyPress = function(e)
+ {
+ var self = this;
+
+ if(self.isDropdownVisible())
+ self.selectFromDropdown();
+ };
+
+ /**
+ * Reacts to the `escapeKeyPress` event triggered by the TextExt core. Hides the dropdown
+ * if it's currently visible.
+ *
+ * @signature TextExtAutocomplete.onEscapeKeyPress(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onEscapeKeyPress
+ */
+ p.onEscapeKeyPress = function(e)
+ {
+ var self = this;
+
+ if(self.isDropdownVisible())
+ self.trigger(EVENT_HIDE_DROPDOWN);
+ };
+
+ //--------------------------------------------------------------------------------
+ // Core functionality
+
+ /**
+ * Positions dropdown either below or above the input based on the `autocomplete.dropdown.position`
+ * option specified, which could be either `above` or `below`.
+ *
+ * @signature TextExtAutocomplete.positionDropdown()
+ *
+ * @author agorbatchev
+ * @date 2011/08/15
+ * @id TextExtAutocomplete.positionDropdown
+ */
+ p.positionDropdown = function()
+ {
+ var self = this,
+ container = self.containerElement(),
+ direction = self.opts(OPT_POSITION),
+ height = self.core().wrapElement().outerHeight(),
+ css = {}
+ ;
+
+ css[direction === POSITION_ABOVE ? 'bottom' : 'top'] = height + 'px';
+ container.css(css);
+ };
+
+ /**
+ * Returns list of all the suggestion HTML elements in the dropdown.
+ *
+ * @signature TextExtAutocomplete.suggestionElements()
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.suggestionElements
+ */
+ p.suggestionElements = function()
+ {
+ return this.containerElement().find(CSS_DOT_SUGGESTION);
+ };
+
+
+ /**
+ * Highlights specified suggestion as selected in the dropdown.
+ *
+ * @signature TextExtAutocomplete.setSelectedSuggestion(suggestion)
+ *
+ * @param suggestion {Object} Suggestion object. With the default `ItemManager` this
+ * is expected to be a string, anything else with custom implementations.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.setSelectedSuggestion
+ */
+ p.setSelectedSuggestion = function(suggestion)
+ {
+ if(!suggestion)
+ return;
+
+ var self = this,
+ all = self.suggestionElements(),
+ target = all.first(),
+ item, i
+ ;
+
+ self.clearSelected();
+
+ for(i = 0; i < all.length; i++)
+ {
+ item = $(all[i]);
+
+ if(self.itemManager().compareItems(item.data(CSS_SUGGESTION), suggestion))
+ {
+ target = item.addClass(CSS_SELECTED);
+ break;
+ }
+ }
+
+ target.addClass(CSS_SELECTED);
+ self.scrollSuggestionIntoView(target);
+ };
+
+ /**
+ * Returns the first suggestion HTML element from the dropdown that is highlighted as selected.
+ *
+ * @signature TextExtAutocomplete.selectedSuggestionElement()
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.selectedSuggestionElement
+ */
+ p.selectedSuggestionElement = function()
+ {
+ return this.suggestionElements().filter(CSS_DOT_SELECTED).first();
+ };
+
+ /**
+ * Returns `true` if dropdown is currently visible, `false` otherwise.
+ *
+ * @signature TextExtAutocomplete.isDropdownVisible()
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.isDropdownVisible
+ */
+ p.isDropdownVisible = function()
+ {
+ return this.containerElement().is(':visible') === true;
+ };
+
+ /**
+ * Reacts to the `getFormData` event triggered by the core. Returns data with the
+ * weight of 100 to be *less than the Tags plugin* data weight. The weights system is
+ * covered in greater detail in the [`getFormData`][1] event documentation.
+ *
+ * [1]: /manual/textext.html#getformdata
+ *
+ * @signature TextExtAutocomplete.onGetFormData(e, data, keyCode)
+ *
+ * @param e {Object} jQuery event.
+ * @param data {Object} Data object to be populated.
+ * @param keyCode {Number} Key code that triggered the original update request.
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExtAutocomplete.onGetFormData
+ */
+ p.onGetFormData = function(e, data, keyCode)
+ {
+ var self = this,
+ val = self.val(),
+ inputValue = val,
+ formValue = val
+ ;
+ data[100] = self.formDataObject(inputValue, formValue);
+ };
+
+ /**
+ * Returns initialization priority of the Autocomplete plugin which is expected to be
+ * *greater than the Tags plugin* because of the dependencies. The value is 200.
+ *
+ * @signature TextExtAutocomplete.initPriority()
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExtAutocomplete.initPriority
+ */
+ p.initPriority = function()
+ {
+ return 200;
+ };
+
+ /**
+ * Reacts to the `hideDropdown` event and hides the dropdown if it's already visible.
+ *
+ * @signature TextExtAutocomplete.onHideDropdown(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onHideDropdown
+ */
+ p.onHideDropdown = function(e)
+ {
+ this.hideDropdown();
+ };
+
+ /**
+ * Reacts to the 'toggleDropdown` event and shows or hides the dropdown depending if
+ * it's currently hidden or visible.
+ *
+ * @signature TextExtAutocomplete.onToggleDropdown(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/12/27
+ * @id TextExtAutocomplete.onToggleDropdown
+ * @version 1.1.0
+ */
+ p.onToggleDropdown = function(e)
+ {
+ var self = this;
+ self.trigger(self.containerElement().is(':visible') ? EVENT_HIDE_DROPDOWN : EVENT_SHOW_DROPDOWN);
+ };
+
+ /**
+ * Reacts to the `showDropdown` event and shows the dropdown if it's not already visible.
+ * It's possible to pass a render callback function which will be called instead of the
+ * default `TextExtAutocomplete.renderSuggestions()`.
+ *
+ * If no suggestion were previously loaded, it will fire `getSuggestions` event and exit.
+ *
+ * Here's how another plugin should trigger this event with the optional render callback:
+ *
+ * this.trigger('showDropdown', function(autocomplete)
+ * {
+ * autocomplete.clearItems();
+ * var node = autocomplete.addDropdownItem('Item ');
+ * node.addClass('new-look');
+ * });
+ *
+ * @signature TextExtAutocomplete.onShowDropdown(e, renderCallback)
+ *
+ * @param e {Object} jQuery event.
+ * @param renderCallback {Function} Optional callback function which would be used to
+ * render dropdown items. As a first argument, reference to the current instance of
+ * Autocomplete plugin will be supplied. It's assumed, that if this callback is provided
+ * rendering will be handled completely manually.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onShowDropdown
+ */
+ p.onShowDropdown = function(e, renderCallback)
+ {
+ var self = this,
+ current = self.selectedSuggestionElement().data(CSS_SUGGESTION),
+ suggestions = self._suggestions
+ ;
+
+ if(!suggestions)
+ return self.trigger(EVENT_GET_SUGGESTIONS);
+
+ if($.isFunction(renderCallback))
+ {
+ renderCallback(self);
+ }
+ else
+ {
+ self.renderSuggestions(self._suggestions);
+ self.toggleNextSuggestion();
+ }
+
+ self.showDropdown(self.containerElement());
+ self.setSelectedSuggestion(current);
+ };
+
+ /**
+ * Reacts to the `setSuggestions` event. Expects to recieve the payload as the second argument
+ * in the following structure:
+ *
+ * {
+ * result : [ "item1", "item2" ],
+ * showHideDropdown : false
+ * }
+ *
+ * Notice the optional `showHideDropdown` option. By default, ie without the `showHideDropdown`
+ * value the method will trigger either `showDropdown` or `hideDropdown` depending if there are
+ * suggestions. If set to `false`, no event is triggered.
+ *
+ * @signature TextExtAutocomplete.onSetSuggestions(e, data)
+ *
+ * @param data {Object} Data payload.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.onSetSuggestions
+ */
+ p.onSetSuggestions = function(e, data)
+ {
+ var self = this,
+ suggestions = self._suggestions = data.result
+ ;
+
+ if(data.showHideDropdown !== false)
+ self.trigger(suggestions === null || suggestions.length === 0 ? EVENT_HIDE_DROPDOWN : EVENT_SHOW_DROPDOWN);
+ };
+
+ /**
+ * Prepears for and triggers the `getSuggestions` event with the `{ query : {String} }` as second
+ * argument.
+ *
+ * @signature TextExtAutocomplete.getSuggestions()
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.getSuggestions
+ */
+ p.getSuggestions = function()
+ {
+ var self = this,
+ val = self.val()
+ ;
+
+ if(self._previousInputValue == val)
+ return;
+
+ // if user clears input, then we want to select first suggestion
+ // instead of the last one
+ if(val == '')
+ current = null;
+
+ self._previousInputValue = val;
+ self.trigger(EVENT_GET_SUGGESTIONS, { query : val });
+ };
+
+ /**
+ * Removes all HTML suggestion items from the dropdown.
+ *
+ * @signature TextExtAutocomplete.clearItems()
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.clearItems
+ */
+ p.clearItems = function()
+ {
+ this.containerElement().find('.text-list').children().remove();
+ };
+
+ /**
+ * Clears all and renders passed suggestions.
+ *
+ * @signature TextExtAutocomplete.renderSuggestions(suggestions)
+ *
+ * @param suggestions {Array} List of suggestions to render.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.renderSuggestions
+ */
+ p.renderSuggestions = function(suggestions)
+ {
+ var self = this;
+
+ self.clearItems();
+
+ $.each(suggestions || [], function(index, item)
+ {
+ self.addSuggestion(item);
+ });
+ };
+
+ /**
+ * Shows the dropdown.
+ *
+ * @signature TextExtAutocomplete.showDropdown()
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.showDropdown
+ */
+ p.showDropdown = function()
+ {
+ this.containerElement().show();
+ };
+
+ /**
+ * Hides the dropdown.
+ *
+ * @signature TextExtAutocomplete.hideDropdown()
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.hideDropdown
+ */
+ p.hideDropdown = function()
+ {
+ var self = this,
+ dropdown = self.containerElement()
+ ;
+
+ self._previousInputValue = null;
+ dropdown.hide();
+ };
+
+ /**
+ * Adds single suggestion to the bottom of the dropdown. Uses `ItemManager.itemToString()` to
+ * serialize provided suggestion to string.
+ *
+ * @signature TextExtAutocomplete.addSuggestion(suggestion)
+ *
+ * @param suggestion {Object} Suggestion item. By default expected to be a string.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.addSuggestion
+ */
+ p.addSuggestion = function(suggestion)
+ {
+ var self = this,
+ renderer = self.opts(OPT_RENDER),
+ node = self.addDropdownItem(renderer ? renderer.call(self, suggestion) : self.itemManager().itemToString(suggestion))
+ ;
+
+ node.data(CSS_SUGGESTION, suggestion);
+ };
+
+ /**
+ * Adds and returns HTML node to the bottom of the dropdown.
+ *
+ * @signature TextExtAutocomplete.addDropdownItem(html)
+ *
+ * @param html {String} HTML to be inserted into the item.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.addDropdownItem
+ */
+ p.addDropdownItem = function(html)
+ {
+ var self = this,
+ container = self.containerElement().find('.text-list'),
+ node = $(self.opts(OPT_HTML_SUGGESTION))
+ ;
+
+ node.find('.text-label').html(html);
+ container.append(node);
+ return node;
+ };
+
+ /**
+ * Removes selection highlight from all suggestion elements.
+ *
+ * @signature TextExtAutocomplete.clearSelected()
+ *
+ * @author agorbatchev
+ * @date 2011/08/02
+ * @id TextExtAutocomplete.clearSelected
+ */
+ p.clearSelected = function()
+ {
+ this.suggestionElements().removeClass(CSS_SELECTED);
+ };
+
+ /**
+ * Selects next suggestion relative to the current one. If there's no
+ * currently selected suggestion, it will select the first one. Selected
+ * suggestion will always be scrolled into view.
+ *
+ * @signature TextExtAutocomplete.toggleNextSuggestion()
+ *
+ * @author agorbatchev
+ * @date 2011/08/02
+ * @id TextExtAutocomplete.toggleNextSuggestion
+ */
+ p.toggleNextSuggestion = function()
+ {
+ var self = this,
+ selected = self.selectedSuggestionElement(),
+ next
+ ;
+
+ if(selected.length > 0)
+ {
+ next = selected.next();
+
+ if(next.length > 0)
+ selected.removeClass(CSS_SELECTED);
+ }
+ else
+ {
+ next = self.suggestionElements().first();
+ }
+
+ next.addClass(CSS_SELECTED);
+ self.scrollSuggestionIntoView(next);
+ };
+
+ /**
+ * Selects previous suggestion relative to the current one. Selected
+ * suggestion will always be scrolled into view.
+ *
+ * @signature TextExtAutocomplete.togglePreviousSuggestion()
+ *
+ * @author agorbatchev
+ * @date 2011/08/02
+ * @id TextExtAutocomplete.togglePreviousSuggestion
+ */
+ p.togglePreviousSuggestion = function()
+ {
+ var self = this,
+ selected = self.selectedSuggestionElement(),
+ prev = selected.prev()
+ ;
+
+ if(prev.length == 0)
+ return;
+
+ self.clearSelected();
+ prev.addClass(CSS_SELECTED);
+ self.scrollSuggestionIntoView(prev);
+ };
+
+ /**
+ * Scrolls specified HTML suggestion element into the view.
+ *
+ * @signature TextExtAutocomplete.scrollSuggestionIntoView(item)
+ *
+ * @param item {HTMLElement} jQuery HTML suggestion element which needs to
+ * scrolled into view.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.scrollSuggestionIntoView
+ */
+ p.scrollSuggestionIntoView = function(item)
+ {
+ var itemHeight = item.outerHeight(),
+ dropdown = this.containerElement(),
+ dropdownHeight = dropdown.innerHeight(),
+ scrollPos = dropdown.scrollTop(),
+ itemTop = (item.position() || {}).top,
+ scrollTo = null,
+ paddingTop = parseInt(dropdown.css('paddingTop'))
+ ;
+
+ if(itemTop == null)
+ return;
+
+ // if scrolling down and item is below the bottom fold
+ if(itemTop + itemHeight > dropdownHeight)
+ scrollTo = itemTop + scrollPos + itemHeight - dropdownHeight + paddingTop;
+
+ // if scrolling up and item is above the top fold
+ if(itemTop < 0)
+ scrollTo = itemTop + scrollPos - paddingTop;
+
+ if(scrollTo != null)
+ dropdown.scrollTop(scrollTo);
+ };
+
+ /**
+ * Uses the value from the text input to finish autocomplete action. Currently selected
+ * suggestion from the dropdown will be used to complete the action. Triggers `hideDropdown`
+ * event.
+ *
+ * @signature TextExtAutocomplete.selectFromDropdown()
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtAutocomplete.selectFromDropdown
+ */
+ p.selectFromDropdown = function()
+ {
+ var self = this,
+ suggestion = self.selectedSuggestionElement().data(CSS_SUGGESTION)
+ ;
+
+ if(suggestion)
+ {
+ self.val(self.itemManager().itemToString(suggestion));
+ self.core().getFormData();
+ }
+
+ self.trigger(EVENT_HIDE_DROPDOWN);
+ };
+
+ /**
+ * Determines if the specified HTML element is within the TextExt core wrap HTML element.
+ *
+ * @signature TextExtAutocomplete.withinWrapElement(element)
+ *
+ * @param element {HTMLElement} element to check if contained by wrap element
+ *
+ * @author adamayres
+ * @version 1.3.0
+ * @date 2012/01/15
+ * @id TextExtAutocomplete.withinWrapElement
+ */
+ p.withinWrapElement = function(element)
+ {
+ return this.core().wrapElement().find(element).size() > 0;
+ }
+})(jQuery);
diff --git a/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.clear.js b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.clear.js
new file mode 100644
index 0000000..da97656
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.clear.js
@@ -0,0 +1,116 @@
+/**
+ * jQuery TextExt Plugin
+ * http://alexgorbatchev.com/textext
+ *
+ * @version 1.3.1
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
+ * @license MIT License
+ */
+(function($)
+{
+ /**
+ * Displays a clear search button.
+ *
+ * @author mreinstein
+ * @date 2012/02/19
+ * @id TextExtClear
+ */
+ function TextExtClear() {};
+
+ $.fn.textext.TextExtClear = TextExtClear;
+ $.fn.textext.addPlugin('clear', TextExtClear);
+
+ var p = TextExtClear.prototype,
+ /**
+ * Clear plugin only has one option and that is its HTML template. It could be
+ * changed when passed to the `$().textext()` function. For example:
+ *
+ * $('textarea').textext({
+ * plugins: 'clear',
+ * html: {
+ * clear: " "
+ * }
+ * })
+ *
+ * @author mreinstein
+ * @date 2012/02/19
+ * @id TextExtClear.options
+ */
+
+ /**
+ * HTML source that is used to generate markup required for the clear.
+ *
+ * @name html.clear
+ * @default '
'
+ * @author mreinstein
+ * @date 2012/02/19
+ * @id TextExtClear.options.html.clear
+ */
+ OPT_HTML_CLEAR = 'html.clear',
+
+ DEFAULT_OPTS = {
+ html : {
+ clear : '
'
+ }
+ };
+
+ /**
+ * Initialization method called by the core during plugin instantiation.
+ *
+ * @signature TextExtClear.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/12/27
+ * @id TextExtClear.init
+ */
+ p.init = function(core)
+ {
+ var self = this,
+ clear
+ ;
+
+ self.baseInit(core, DEFAULT_OPTS);
+
+ self._clear = clear = $(self.opts(OPT_HTML_CLEAR));
+ self.core().wrapElement().append(clear);
+ clear.bind('click', function(e) { self.onClearClick(e); });
+ };
+
+ //--------------------------------------------------------------------------------
+ // Event handlers
+
+ /**
+ * Reacts to the `click` event whenever user clicks the clear.
+ *
+ * @signature TextExtClear.onClearClick(e)
+ *
+ * @param e {Object} jQuery event.
+ * @author agorbatchev
+ * @date 2011/12/27
+ * @id TextExtClear.onClearClick
+ */
+ p.onClearClick = function(e)
+ {
+ var self = this;
+
+ // check if the tags plugin is present
+ if(typeof self.core()._plugins.tags != 'undefined')
+ {
+ // it is! remove all tags
+ var elems = self.core()._plugins.tags.tagElements();
+ for(var i =0; i < elems.length;i++)
+ {
+ self.core()._plugins.tags.removeTag($(elems[i]));
+ }
+ }
+ // clear the text from the search area
+ self.val('');
+ self.core().getFormData();
+ };
+
+ //--------------------------------------------------------------------------------
+ // Core functionality
+
+})(jQuery);
\ No newline at end of file
diff --git a/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.filter.js b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.filter.js
new file mode 100644
index 0000000..6f973e6
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.filter.js
@@ -0,0 +1,242 @@
+/**
+ * jQuery TextExt Plugin
+ * http://textextjs.com
+ *
+ * @version 1.3.1
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
+ * @license MIT License
+ */
+(function($)
+{
+ /**
+ * The Filter plugin introduces ability to limit input that the text field
+ * will accept. If the Tags plugin is used, Filter plugin will limit which
+ * tags it's possible to add.
+ *
+ * The list of allowed items can be either specified through the
+ * options, can come from the Suggestions plugin or be loaded by the Ajax
+ * plugin. All these plugins have one thing in common -- they
+ * trigger `setSuggestions` event which the Filter plugin is expecting.
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFilter
+ */
+ function TextExtFilter() {};
+
+ $.fn.textext.TextExtFilter = TextExtFilter;
+ $.fn.textext.addPlugin('filter', TextExtFilter);
+
+ var p = TextExtFilter.prototype,
+
+ /**
+ * Filter plugin options are grouped under `filter` when passed to the
+ * `$().textext()` function. For example:
+ *
+ * $('textarea').textext({
+ * plugins: 'filter',
+ * filter: {
+ * items: [ "item1", "item2" ]
+ * }
+ * })
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFilter.options
+ */
+
+ /**
+ * This is a toggle switch to enable or disable the Filter plugin. The value is checked
+ * each time at the top level which allows you to toggle this setting on the fly.
+ *
+ * @name filter.enabled
+ * @default true
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFilter.options.enabled
+ */
+ OPT_ENABLED = 'filter.enabled',
+
+ /**
+ * Arra of items that the Filter plugin will allow the Tag plugin to add to the list of
+ * its resut tags. Each item by default is expected to be a string which default `ItemManager`
+ * can work with. You can change the item type by supplying custom `ItemManager`.
+ *
+ * @name filter.items
+ * @default null
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFilter.options.items
+ */
+ OPT_ITEMS = 'filter.items',
+
+ /**
+ * Filter plugin dispatches and reacts to the following events.
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFilter.events
+ */
+
+ /**
+ * Filter plugin reacts to the `isTagAllowed` event triggered by the Tags plugin before
+ * adding a new tag to the list. If the new tag is among the `items` specified in options,
+ * then the new tag will be allowed.
+ *
+ * @name isTagAllowed
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFilter.events.isTagAllowed
+ */
+
+ /**
+ * Filter plugin reacts to the `setSuggestions` event triggered by other plugins like
+ * Suggestions and Ajax.
+ *
+ * However, event if this event is handled and items are passed with it and stored, if `items`
+ * option was supplied, it will always take precedense.
+ *
+ * @name setSuggestions
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFilter.events.setSuggestions
+ */
+
+ DEFAULT_OPTS = {
+ filter : {
+ enabled : true,
+ items : null
+ }
+ }
+ ;
+
+ /**
+ * Initialization method called by the core during plugin instantiation.
+ *
+ * @signature TextExtFilter.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFilter.init
+ */
+ p.init = function(core)
+ {
+ var self = this;
+ self.baseInit(core, DEFAULT_OPTS);
+
+ self.on({
+ getFormData : self.onGetFormData,
+ isTagAllowed : self.onIsTagAllowed,
+ setSuggestions : self.onSetSuggestions
+ });
+
+ self._suggestions = null;
+ };
+
+ //--------------------------------------------------------------------------------
+ // Core functionality
+
+ /**
+ * Reacts to the [`getFormData`][1] event triggered by the core. Returns data with the
+ * weight of 200 to be *greater than the Autocomplete plugins* data weights.
+ * The weights system is covered in greater detail in the [`getFormData`][1] event
+ * documentation.
+ *
+ * This method does nothing if Tags tag is also present.
+ *
+ * [1]: /manual/textext.html#getformdata
+ *
+ * @signature TextExtFilter.onGetFormData(e, data, keyCode)
+ *
+ * @param e {Object} jQuery event.
+ * @param data {Object} Data object to be populated.
+ * @param keyCode {Number} Key code that triggered the original update request.
+ *
+ * @author agorbatchev
+ * @date 2011/12/28
+ * @id TextExtFilter.onGetFormData
+ * @version 1.1
+ */
+ p.onGetFormData = function(e, data, keyCode)
+ {
+ var self = this,
+ val = self.val(),
+ inputValue = val,
+ formValue = ''
+ ;
+
+ if(!self.core().hasPlugin('tags'))
+ {
+ if(self.isValueAllowed(inputValue))
+ formValue = val;
+
+ data[300] = self.formDataObject(inputValue, formValue);
+ }
+ };
+
+ /**
+ * Checks given value if it's present in `filterItems` or was loaded for the Autocomplete
+ * or by the Suggestions plugins. `value` is compared to each item using `ItemManager.compareItems`
+ * method which is currently attached to the core. Returns `true` if value is known or
+ * Filter plugin is disabled.
+ *
+ * @signature TextExtFilter.isValueAllowed(value)
+ *
+ * @param value {Object} Value to check.
+ *
+ * @author agorbatchev
+ * @date 2011/12/28
+ * @id TextExtFilter.isValueAllowed
+ * @version 1.1
+ */
+ p.isValueAllowed = function(value)
+ {
+ var self = this,
+ list = self.opts('filterItems') || self._suggestions || [],
+ itemManager = self.itemManager(),
+ result = !self.opts(OPT_ENABLED), // if disabled, should just return true
+ i
+ ;
+
+ for(i = 0; i < list.length && !result; i++)
+ if(itemManager.compareItems(value, list[i]))
+ result = true;
+
+ return result;
+ };
+
+ /**
+ * Handles `isTagAllowed` event dispatched by the Tags plugin. If supplied tag is not
+ * in the `items` list, method sets `result` on the `data` argument to `false`.
+ *
+ * @signature TextExtFilter.onIsTagAllowed(e, data)
+ *
+ * @param e {Object} jQuery event.
+ * @param data {Object} Payload in the following format : `{ tag : {Object}, result : {Boolean} }`.
+ * @author agorbatchev
+ * @date 2011/08/04
+ * @id TextExtFilter.onIsTagAllowed
+ */
+ p.onIsTagAllowed = function(e, data)
+ {
+ data.result = this.isValueAllowed(data.tag);
+ };
+
+ /**
+ * Reacts to the `setSuggestions` events and stores supplied suggestions for future use.
+ *
+ * @signature TextExtFilter.onSetSuggestions(e, data)
+ *
+ * @param e {Object} jQuery event.
+ * @param data {Object} Payload in the following format : `{ result : {Array} } }`.
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFilter.onSetSuggestions
+ */
+ p.onSetSuggestions = function(e, data)
+ {
+ this._suggestions = data.result;
+ };
+})(jQuery);
diff --git a/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.focus.js b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.focus.js
new file mode 100644
index 0000000..5f7f357
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.focus.js
@@ -0,0 +1,174 @@
+/**
+ * jQuery TextExt Plugin
+ * http://textextjs.com
+ *
+ * @version 1.3.1
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
+ * @license MIT License
+ */
+(function($)
+{
+ /**
+ * Focus plugin displays a visual effect whenever user sets focus
+ * into the text area.
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFocus
+ */
+ function TextExtFocus() {};
+
+ $.fn.textext.TextExtFocus = TextExtFocus;
+ $.fn.textext.addPlugin('focus', TextExtFocus);
+
+ var p = TextExtFocus.prototype,
+ /**
+ * Focus plugin only has one option and that is its HTML template. It could be
+ * changed when passed to the `$().textext()` function. For example:
+ *
+ * $('textarea').textext({
+ * plugins: 'focus',
+ * html: {
+ * focus: " "
+ * }
+ * })
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFocus.options
+ */
+
+ /**
+ * HTML source that is used to generate markup required for the focus effect.
+ *
+ * @name html.focus
+ * @default '
'
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFocus.options.html.focus
+ */
+ OPT_HTML_FOCUS = 'html.focus',
+
+ /**
+ * Focus plugin dispatches or reacts to the following events.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtFocus.events
+ */
+
+ /**
+ * Focus plugin reacts to the `focus` event and shows the markup generated from
+ * the `html.focus` option.
+ *
+ * @name focus
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFocus.events.focus
+ */
+
+ /**
+ * Focus plugin reacts to the `blur` event and hides the effect.
+ *
+ * @name blur
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFocus.events.blur
+ */
+
+ DEFAULT_OPTS = {
+ html : {
+ focus : '
'
+ }
+ }
+ ;
+
+ /**
+ * Initialization method called by the core during plugin instantiation.
+ *
+ * @signature TextExtFocus.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtFocus.init
+ */
+ p.init = function(core)
+ {
+ var self = this;
+
+ self.baseInit(core, DEFAULT_OPTS);
+ self.core().wrapElement().append(self.opts(OPT_HTML_FOCUS));
+ self.on({
+ blur : self.onBlur,
+ focus : self.onFocus
+ });
+
+ self._timeoutId = 0;
+ };
+
+ //--------------------------------------------------------------------------------
+ // Event handlers
+
+ /**
+ * Reacts to the `blur` event and hides the focus effect with a slight delay which
+ * allows quick refocusing without effect blinking in and out.
+ *
+ * @signature TextExtFocus.onBlur(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/08
+ * @id TextExtFocus.onBlur
+ */
+ p.onBlur = function(e)
+ {
+ var self = this;
+
+ clearTimeout(self._timeoutId);
+
+ self._timeoutId = setTimeout(function()
+ {
+ self.getFocus().hide();
+ },
+ 100);
+ };
+
+ /**
+ * Reacts to the `focus` event and shows the focus effect.
+ *
+ * @signature TextExtFocus.onFocus
+ *
+ * @param e {Object} jQuery event.
+ * @author agorbatchev
+ * @date 2011/08/08
+ * @id TextExtFocus.onFocus
+ */
+ p.onFocus = function(e)
+ {
+ var self = this;
+
+ clearTimeout(self._timeoutId);
+
+ self.getFocus().show();
+ };
+
+ //--------------------------------------------------------------------------------
+ // Core functionality
+
+ /**
+ * Returns focus effect HTML element.
+ *
+ * @signature TextExtFocus.getFocus()
+ *
+ * @author agorbatchev
+ * @date 2011/08/08
+ * @id TextExtFocus.getFocus
+ */
+ p.getFocus = function()
+ {
+ return this.core().wrapElement().find('.text-focus');
+ };
+})(jQuery);
diff --git a/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.prompt.js b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.prompt.js
new file mode 100644
index 0000000..a40e71d
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.prompt.js
@@ -0,0 +1,309 @@
+/**
+ * jQuery TextExt Plugin
+ * http://textextjs.com
+ *
+ * @version 1.3.1
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
+ * @license MIT License
+ */
+(function($)
+{
+ /**
+ * Prompt plugin displays a visual user propmpt in the text input area. If user focuses
+ * on the input, the propt is hidden and only shown again when user focuses on another
+ * element and text input doesn't have a value.
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtPrompt
+ */
+ function TextExtPrompt() {};
+
+ $.fn.textext.TextExtPrompt = TextExtPrompt;
+ $.fn.textext.addPlugin('prompt', TextExtPrompt);
+
+ var p = TextExtPrompt.prototype,
+
+ CSS_HIDE_PROMPT = 'text-hide-prompt',
+
+ /**
+ * Prompt plugin has options to change the prompt label and its HTML template. The options
+ * could be changed when passed to the `$().textext()` function. For example:
+ *
+ * $('textarea').textext({
+ * plugins: 'prompt',
+ * prompt: 'Your email address'
+ * })
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtPrompt.options
+ */
+
+ /**
+ * Prompt message that is displayed to the user whenever there's no value in the input.
+ *
+ * @name prompt
+ * @default 'Awaiting input...'
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtPrompt.options.prompt
+ */
+ OPT_PROMPT = 'prompt',
+
+ /**
+ * HTML source that is used to generate markup required for the prompt effect.
+ *
+ * @name html.prompt
+ * @default '
'
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtPrompt.options.html.prompt
+ */
+ OPT_HTML_PROMPT = 'html.prompt',
+
+ /**
+ * Prompt plugin dispatches or reacts to the following events.
+ * @id TextExtPrompt.events
+ */
+
+ /**
+ * Prompt plugin reacts to the `focus` event and hides the markup generated from
+ * the `html.prompt` option.
+ *
+ * @name focus
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtPrompt.events.focus
+ */
+
+ /**
+ * Prompt plugin reacts to the `blur` event and shows the prompt back if user
+ * hasn't entered any value.
+ *
+ * @name blur
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtPrompt.events.blur
+ */
+
+ DEFAULT_OPTS = {
+ prompt : 'Awaiting input...',
+
+ html : {
+ prompt : '
'
+ }
+ }
+ ;
+
+ /**
+ * Initialization method called by the core during plugin instantiation.
+ *
+ * @signature TextExtPrompt.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtPrompt.init
+ */
+ p.init = function(core)
+ {
+ var self = this,
+ placeholderKey = 'placeholder',
+ container,
+ prompt
+ ;
+
+ self.baseInit(core, DEFAULT_OPTS);
+
+ container = $(self.opts(OPT_HTML_PROMPT));
+ $(self).data('container', container);
+
+ self.core().wrapElement().append(container);
+ self.setPrompt(self.opts(OPT_PROMPT));
+
+ prompt = core.input().attr(placeholderKey);
+
+ if(!prompt)
+ prompt = self.opts(OPT_PROMPT);
+
+ // clear placeholder attribute if set
+ core.input().attr(placeholderKey, '');
+
+ if(prompt)
+ self.setPrompt(prompt);
+
+ if($.trim(self.val()).length > 0)
+ self.hidePrompt();
+
+ self.on({
+ blur : self.onBlur,
+ focus : self.onFocus,
+ postInvalidate : self.onPostInvalidate,
+ postInit : self.onPostInit
+ });
+ };
+
+ //--------------------------------------------------------------------------------
+ // Event handlers
+
+ /**
+ * Reacts to the `postInit` and configures the plugin for initial display.
+ *
+ * @signature TextExtPrompt.onPostInit(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/24
+ * @id TextExtPrompt.onPostInit
+ */
+ p.onPostInit = function(e)
+ {
+ this.invalidateBounds();
+ };
+
+ /**
+ * Reacts to the `postInvalidate` and insures that prompt display remains correct.
+ *
+ * @signature TextExtPrompt.onPostInvalidate(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/24
+ * @id TextExtPrompt.onPostInvalidate
+ */
+ p.onPostInvalidate = function(e)
+ {
+ this.invalidateBounds();
+ };
+
+ /**
+ * Repositions the prompt to make sure it's always at the same place as in the text input carret.
+ *
+ * @signature TextExtPrompt.invalidateBounds()
+ *
+ * @author agorbatchev
+ * @date 2011/08/24
+ * @id TextExtPrompt.invalidateBounds
+ */
+ p.invalidateBounds = function()
+ {
+ var self = this,
+ input = self.input()
+ ;
+
+ self.containerElement().css({
+ paddingLeft : input.css('paddingLeft'),
+ paddingTop : input.css('paddingTop')
+ });
+ };
+
+ /**
+ * Reacts to the `blur` event and shows the prompt effect with a slight delay which
+ * allows quick refocusing without effect blinking in and out.
+ *
+ * The prompt is restored if the text box has no value.
+ *
+ * @signature TextExtPrompt.onBlur(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/08
+ * @id TextExtPrompt.onBlur
+ */
+ p.onBlur = function(e)
+ {
+ var self = this;
+
+ self.startTimer('prompt', 0.1, function()
+ {
+ self.showPrompt();
+ });
+ };
+
+ /**
+ * Shows prompt HTML element.
+ *
+ * @signature TextExtPrompt.showPrompt()
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExtPrompt.showPrompt
+ */
+ p.showPrompt = function()
+ {
+ var self = this,
+ input = self.input()
+ ;
+
+ if($.trim(self.val()).length === 0 && !input.is(':focus'))
+ self.containerElement().removeClass(CSS_HIDE_PROMPT);
+ };
+
+ /**
+ * Hides prompt HTML element.
+ *
+ * @signature TextExtPrompt.hidePrompt()
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExtPrompt.hidePrompt
+ */
+ p.hidePrompt = function()
+ {
+ this.stopTimer('prompt');
+ this.containerElement().addClass(CSS_HIDE_PROMPT);
+ };
+
+ /**
+ * Reacts to the `focus` event and hides the prompt effect.
+ *
+ * @signature TextExtPrompt.onFocus
+ *
+ * @param e {Object} jQuery event.
+ * @author agorbatchev
+ * @date 2011/08/08
+ * @id TextExtPrompt.onFocus
+ */
+ p.onFocus = function(e)
+ {
+ this.hidePrompt();
+ };
+
+ //--------------------------------------------------------------------------------
+ // Core functionality
+
+ /**
+ * Sets the prompt display to the specified string.
+ *
+ * @signature TextExtPrompt.setPrompt(str)
+ *
+ * @oaram str {String} String that will be displayed in the prompt.
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtPrompt.setPrompt
+ */
+ p.setPrompt = function(str)
+ {
+ this.containerElement().text(str);
+ };
+
+ /**
+ * Returns prompt effect HTML element.
+ *
+ * @signature TextExtPrompt.containerElement()
+ *
+ * @author agorbatchev
+ * @date 2011/08/08
+ * @id TextExtPrompt.containerElement
+ */
+ p.containerElement = function()
+ {
+ return $(this).data('container');
+ };
+})(jQuery);
diff --git a/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.suggestions.js b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.suggestions.js
new file mode 100644
index 0000000..1e04613
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.suggestions.js
@@ -0,0 +1,175 @@
+/**
+ * jQuery TextExt Plugin
+ * http://textextjs.com
+ *
+ * @version 1.3.1
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
+ * @license MIT License
+ */
+(function($)
+{
+ /**
+ * Suggestions plugin allows to easily specify the list of suggestion items that the
+ * Autocomplete plugin would present to the user.
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtSuggestions
+ */
+ function TextExtSuggestions() {};
+
+ $.fn.textext.TextExtSuggestions = TextExtSuggestions;
+ $.fn.textext.addPlugin('suggestions', TextExtSuggestions);
+
+ var p = TextExtSuggestions.prototype,
+ /**
+ * Suggestions plugin only has one option and that is to set suggestion items. It could be
+ * changed when passed to the `$().textext()` function. For example:
+ *
+ * $('textarea').textext({
+ * plugins: 'suggestions',
+ * suggestions: [ "item1", "item2" ]
+ * })
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtSuggestions.options
+ */
+
+ /**
+ * List of items that Autocomplete plugin would display in the dropdown.
+ *
+ * @name suggestions
+ * @default null
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtSuggestions.options.suggestions
+ */
+ OPT_SUGGESTIONS = 'suggestions',
+
+ /**
+ * Suggestions plugin dispatches or reacts to the following events.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtSuggestions.events
+ */
+
+ /**
+ * Suggestions plugin reacts to the `getSuggestions` event and returns `suggestions` items
+ * from the options.
+ *
+ * @name getSuggestions
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtSuggestions.events.getSuggestions
+ */
+
+ /**
+ * Suggestions plugin triggers the `setSuggestions` event to pass its own list of `Suggestions`
+ * to the Autocomplete plugin.
+ *
+ * @name setSuggestions
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtSuggestions.events.setSuggestions
+ */
+
+ /**
+ * Suggestions plugin reacts to the `postInit` event to pass its list of `suggestions` to the
+ * Autocomplete right away.
+ *
+ * @name postInit
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtSuggestions.events.postInit
+ */
+
+ DEFAULT_OPTS = {
+ suggestions : null
+ }
+ ;
+
+ /**
+ * Initialization method called by the core during plugin instantiation.
+ *
+ * @signature TextExtSuggestions.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/08/18
+ * @id TextExtSuggestions.init
+ */
+ p.init = function(core)
+ {
+ var self = this;
+
+ self.baseInit(core, DEFAULT_OPTS);
+
+ self.on({
+ getSuggestions : self.onGetSuggestions,
+ postInit : self.onPostInit
+ });
+ };
+
+ /**
+ * Triggers `setSuggestions` and passes supplied suggestions to the Autocomplete plugin.
+ *
+ * @signature TextExtSuggestions.setSuggestions(suggestions, showHideDropdown)
+ *
+ * @param suggestions {Array} List of suggestions. With the default `ItemManager` it should
+ * be a list of strings.
+ * @param showHideDropdown {Boolean} If it's undesirable to show the dropdown right after
+ * suggestions are set, `false` should be passed for this argument.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtSuggestions.setSuggestions
+ */
+ p.setSuggestions = function(suggestions, showHideDropdown)
+ {
+ this.trigger('setSuggestions', { result : suggestions, showHideDropdown : showHideDropdown != false });
+ };
+
+ /**
+ * Reacts to the `postInit` event and triggers `setSuggestions` event to set suggestions list
+ * right after initialization.
+ *
+ * @signature TextExtSuggestions.onPostInit(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtSuggestions.onPostInit
+ */
+ p.onPostInit = function(e)
+ {
+ var self = this;
+ self.setSuggestions(self.opts(OPT_SUGGESTIONS), false);
+ };
+
+ /**
+ * Reacts to the `getSuggestions` event and triggers `setSuggestions` event with the list
+ * of `suggestions` specified in the options.
+ *
+ * @signature TextExtSuggestions.onGetSuggestions(e, data)
+ *
+ * @param e {Object} jQuery event.
+ * @param data {Object} Payload from the `getSuggestions` event with the user query, eg `{ query: {String} }`.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtSuggestions.onGetSuggestions
+ */
+ p.onGetSuggestions = function(e, data)
+ {
+ var self = this,
+ suggestions = self.opts(OPT_SUGGESTIONS)
+ ;
+
+ suggestions.sort();
+ self.setSuggestions(self.itemManager().filter(suggestions, data.query));
+ };
+})(jQuery);
diff --git a/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.tags.js b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.tags.js
new file mode 100644
index 0000000..26de5bc
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/js/textext.plugin.tags.js
@@ -0,0 +1,698 @@
+/**
+ * jQuery TextExt Plugin
+ * http://textextjs.com
+ *
+ * @version 1.3.1
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
+ * @license MIT License
+ */
+(function($)
+{
+ /**
+ * Tags plugin brings in the traditional tag functionality where user can assemble and
+ * edit list of tags. Tags plugin works especially well together with Autocomplete, Filter,
+ * Suggestions and Ajax plugins to provide full spectrum of features. It can also work on
+ * its own and just do one thing -- tags.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags
+ */
+ function TextExtTags() {};
+
+ $.fn.textext.TextExtTags = TextExtTags;
+ $.fn.textext.addPlugin('tags', TextExtTags);
+
+ var p = TextExtTags.prototype,
+
+ CSS_DOT = '.',
+ CSS_TAGS_ON_TOP = 'text-tags-on-top',
+ CSS_DOT_TAGS_ON_TOP = CSS_DOT + CSS_TAGS_ON_TOP,
+ CSS_TAG = 'text-tag',
+ CSS_DOT_TAG = CSS_DOT + CSS_TAG,
+ CSS_TAGS = 'text-tags',
+ CSS_DOT_TAGS = CSS_DOT + CSS_TAGS,
+ CSS_LABEL = 'text-label',
+ CSS_DOT_LABEL = CSS_DOT + CSS_LABEL,
+ CSS_REMOVE = 'text-remove',
+ CSS_DOT_REMOVE = CSS_DOT + CSS_REMOVE,
+
+ /**
+ * Tags plugin options are grouped under `tags` when passed to the
+ * `$().textext()` function. For example:
+ *
+ * $('textarea').textext({
+ * plugins: 'tags',
+ * tags: {
+ * items: [ "tag1", "tag2" ]
+ * }
+ * })
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.options
+ */
+
+ /**
+ * This is a toggle switch to enable or disable the Tags plugin. The value is checked
+ * each time at the top level which allows you to toggle this setting on the fly.
+ *
+ * @name tags.enabled
+ * @default true
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.options.tags.enabled
+ */
+ OPT_ENABLED = 'tags.enabled',
+
+ /**
+ * Allows to specify tags which will be added to the input by default upon initialization.
+ * Each item in the array must be of the type that current `ItemManager` can understand.
+ * Default type is `String`.
+ *
+ * @name tags.items
+ * @default null
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.options.tags.items
+ */
+ OPT_ITEMS = 'tags.items',
+
+ /**
+ * HTML source that is used to generate a single tag.
+ *
+ * @name html.tag
+ * @default '
'
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.options.html.tag
+ */
+ OPT_HTML_TAG = 'html.tag',
+
+ /**
+ * HTML source that is used to generate container for the tags.
+ *
+ * @name html.tags
+ * @default ''
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.options.html.tags
+ */
+ OPT_HTML_TAGS = 'html.tags',
+
+ /**
+ * Tags plugin dispatches or reacts to the following events.
+ *
+ * @author agorbatchev
+ * @date 2011/08/17
+ * @id TextExtTags.events
+ */
+
+ /**
+ * Tags plugin triggers the `isTagAllowed` event before adding each tag to the tag list. Other plugins have
+ * an opportunity to interrupt this by setting `result` of the second argument to `false`. For example:
+ *
+ * $('textarea').textext({...}).bind('isTagAllowed', function(e, data)
+ * {
+ * if(data.tag === 'foo')
+ * data.result = false;
+ * })
+ *
+ * The second argument `data` has the following format: `{ tag : {Object}, result : {Boolean} }`. `tag`
+ * property is in the format that the current `ItemManager` can understand.
+ *
+ * @name isTagAllowed
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.events.isTagAllowed
+ */
+ EVENT_IS_TAG_ALLOWED = 'isTagAllowed',
+
+ /**
+ * Tags plugin triggers the `tagClick` event when user clicks on one of the tags. This allows to process
+ * the click and potentially change the value of the tag (for example in case of user feedback).
+ *
+ * $('textarea').textext({...}).bind('tagClick', function(e, tag, value, callback)
+ * {
+ * var newValue = window.prompt('New value', value);
+
+ * if(newValue)
+ * callback(newValue, true);
+ * })
+ *
+ * Callback argument has the following signature:
+ *
+ * function(newValue, refocus)
+ * {
+ * ...
+ * }
+ *
+ * Please check out [example](/manual/examples/tags-changing.html).
+ *
+ * @name tagClick
+ * @version 1.3.0
+ * @author s.stok
+ * @date 2011/01/23
+ * @id TextExtTags.events.tagClick
+ */
+ EVENT_TAG_CLICK = 'tagClick',
+
+ DEFAULT_OPTS = {
+ tags : {
+ enabled : true,
+ items : null
+ },
+
+ html : {
+ tags : '
',
+ tag : ''
+ }
+ }
+ ;
+
+ /**
+ * Initialization method called by the core during plugin instantiation.
+ *
+ * @signature TextExtTags.init(core)
+ *
+ * @param core {TextExt} Instance of the TextExt core class.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.init
+ */
+ p.init = function(core)
+ {
+ this.baseInit(core, DEFAULT_OPTS);
+ var self = this,
+ input = self.input(),
+ container
+ ;
+
+ if(self.opts(OPT_ENABLED))
+ {
+ container = $(self.opts(OPT_HTML_TAGS));
+ input.after(container);
+
+ $(self).data('container', container);
+
+ self.on({
+ enterKeyPress : self.onEnterKeyPress,
+ backspaceKeyDown : self.onBackspaceKeyDown,
+ preInvalidate : self.onPreInvalidate,
+ postInit : self.onPostInit,
+ getFormData : self.onGetFormData
+ });
+
+ self.on(container, {
+ click : self.onClick,
+ mousemove : self.onContainerMouseMove
+ });
+
+ self.on(input, {
+ mousemove : self.onInputMouseMove
+ });
+ }
+
+ self._originalPadding = {
+ left : parseInt(input.css('paddingLeft') || 0),
+ top : parseInt(input.css('paddingTop') || 0)
+ };
+
+ self._paddingBox = {
+ left : 0,
+ top : 0
+ };
+
+ self.updateFormCache();
+ };
+
+ /**
+ * Returns HTML element in which all tag HTML elements are residing.
+ *
+ * @signature TextExtTags.containerElement()
+ *
+ * @author agorbatchev
+ * @date 2011/08/15
+ * @id TextExtTags.containerElement
+ */
+ p.containerElement = function()
+ {
+ return $(this).data('container');
+ };
+
+ //--------------------------------------------------------------------------------
+ // Event handlers
+
+ /**
+ * Reacts to the `postInit` event triggered by the core and sets default tags
+ * if any were specified.
+ *
+ * @signature TextExtTags.onPostInit(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/09
+ * @id TextExtTags.onPostInit
+ */
+ p.onPostInit = function(e)
+ {
+ var self = this;
+ self.addTags(self.opts(OPT_ITEMS));
+ };
+
+ /**
+ * Reacts to the [`getFormData`][1] event triggered by the core. Returns data with the
+ * weight of 200 to be *greater than the Autocomplete plugin* data weight. The weights
+ * system is covered in greater detail in the [`getFormData`][1] event documentation.
+ *
+ * [1]: /manual/textext.html#getformdata
+ *
+ * @signature TextExtTags.onGetFormData(e, data, keyCode)
+ *
+ * @param e {Object} jQuery event.
+ * @param data {Object} Data object to be populated.
+ * @param keyCode {Number} Key code that triggered the original update request.
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExtTags.onGetFormData
+ */
+ p.onGetFormData = function(e, data, keyCode)
+ {
+ var self = this,
+ inputValue = keyCode === 13 ? '' : self.val(),
+ formValue = self._formData
+ ;
+
+ data[200] = self.formDataObject(inputValue, formValue);
+ };
+
+ /**
+ * Returns initialization priority of the Tags plugin which is expected to be
+ * *less than the Autocomplete plugin* because of the dependencies. The value is
+ * 100.
+ *
+ * @signature TextExtTags.initPriority()
+ *
+ * @author agorbatchev
+ * @date 2011/08/22
+ * @id TextExtTags.initPriority
+ */
+ p.initPriority = function()
+ {
+ return 100;
+ };
+
+ /**
+ * Reacts to user moving mouse over the text area when cursor is over the text
+ * and not over the tags. Whenever mouse cursor is over the area covered by
+ * tags, the tags container is flipped to be on top of the text area which
+ * makes all tags functional with the mouse.
+ *
+ * @signature TextExtTags.onInputMouseMove(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/08
+ * @id TextExtTags.onInputMouseMove
+ */
+ p.onInputMouseMove = function(e)
+ {
+ this.toggleZIndex(e);
+ };
+
+ /**
+ * Reacts to user moving mouse over the tags. Whenever the cursor moves out
+ * of the tags and back into where the text input is happening visually,
+ * the tags container is sent back under the text area which allows user
+ * to interact with the text using mouse cursor as expected.
+ *
+ * @signature TextExtTags.onContainerMouseMove(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/08
+ * @id TextExtTags.onContainerMouseMove
+ */
+ p.onContainerMouseMove = function(e)
+ {
+ this.toggleZIndex(e);
+ };
+
+ /**
+ * Reacts to the `backspaceKeyDown` event. When backspace key is pressed in an empty text field,
+ * deletes last tag from the list.
+ *
+ * @signature TextExtTags.onBackspaceKeyDown(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/02
+ * @id TextExtTags.onBackspaceKeyDown
+ */
+ p.onBackspaceKeyDown = function(e)
+ {
+ var self = this,
+ lastTag = self.tagElements().last()
+ ;
+
+ if(self.val().length == 0)
+ self.removeTag(lastTag);
+ };
+
+ /**
+ * Reacts to the `preInvalidate` event and updates the input box to look like the tags are
+ * positioned inside it.
+ *
+ * @signature TextExtTags.onPreInvalidate(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.onPreInvalidate
+ */
+ p.onPreInvalidate = function(e)
+ {
+ var self = this,
+ lastTag = self.tagElements().last(),
+ pos = lastTag.position()
+ ;
+
+ if(lastTag.length > 0)
+ pos.left += lastTag.innerWidth();
+ else
+ pos = self._originalPadding;
+
+ self._paddingBox = pos;
+
+ self.input().css({
+ paddingLeft : pos.left,
+ paddingTop : pos.top
+ });
+ };
+
+ /**
+ * Reacts to the mouse `click` event.
+ *
+ * @signature TextExtTags.onClick(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.onClick
+ */
+ p.onClick = function(e)
+ {
+ var self = this,
+ core = self.core(),
+ source = $(e.target),
+ focus = 0,
+ tag
+ ;
+
+ if(source.is(CSS_DOT_TAGS))
+ {
+ focus = 1;
+ }
+ else if(source.is(CSS_DOT_REMOVE))
+ {
+ self.removeTag(source.parents(CSS_DOT_TAG + ':first'));
+ focus = 1;
+ }
+ else if(source.is(CSS_DOT_LABEL))
+ {
+ tag = source.parents(CSS_DOT_TAG + ':first');
+ self.trigger(EVENT_TAG_CLICK, tag, tag.data(CSS_TAG), tagClickCallback);
+ }
+
+ function tagClickCallback(newValue, refocus)
+ {
+ tag.data(CSS_TAG, newValue);
+ tag.find(CSS_DOT_LABEL).text(self.itemManager().itemToString(newValue));
+
+ self.updateFormCache();
+ core.getFormData();
+ core.invalidateBounds();
+
+ if(refocus)
+ core.focusInput();
+ }
+
+ if(focus)
+ core.focusInput();
+ };
+
+ /**
+ * Reacts to the `enterKeyPress` event and adds whatever is currently in the text input
+ * as a new tag. Triggers `isTagAllowed` to check if the tag could be added first.
+ *
+ * @signature TextExtTags.onEnterKeyPress(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.onEnterKeyPress
+ */
+ p.onEnterKeyPress = function(e)
+ {
+ var self = this,
+ val = self.val(),
+ tag = self.itemManager().stringToItem(val)
+ ;
+
+ if(self.isTagAllowed(tag))
+ {
+ self.addTags([ tag ]);
+ // refocus the textarea just in case it lost the focus
+ self.core().focusInput();
+ }
+ };
+
+ //--------------------------------------------------------------------------------
+ // Core functionality
+
+ /**
+ * Creates a cache object with all the tags currently added which will be returned
+ * in the `onGetFormData` handler.
+ *
+ * @signature TextExtTags.updateFormCache()
+ *
+ * @author agorbatchev
+ * @date 2011/08/09
+ * @id TextExtTags.updateFormCache
+ */
+ p.updateFormCache = function()
+ {
+ var self = this,
+ result = []
+ ;
+
+ self.tagElements().each(function()
+ {
+ result.push($(this).data(CSS_TAG));
+ });
+
+ // cache the results to be used in the onGetFormData
+ self._formData = result;
+ };
+
+ /**
+ * Toggles tag container to be on top of the text area or under based on where
+ * the mouse cursor is located. When cursor is above the text input and out of
+ * any of the tags, the tags container is sent under the text area. If cursor
+ * is over any of the tags, the tag container is brought to be over the text
+ * area.
+ *
+ * @signature TextExtTags.toggleZIndex(e)
+ *
+ * @param e {Object} jQuery event.
+ *
+ * @author agorbatchev
+ * @date 2011/08/08
+ * @id TextExtTags.toggleZIndex
+ */
+ p.toggleZIndex = function(e)
+ {
+ var self = this,
+ offset = self.input().offset(),
+ mouseX = e.clientX - offset.left,
+ mouseY = e.clientY - offset.top,
+ box = self._paddingBox,
+ container = self.containerElement(),
+ isOnTop = container.is(CSS_DOT_TAGS_ON_TOP),
+ isMouseOverText = mouseX > box.left && mouseY > box.top
+ ;
+
+ if(!isOnTop && !isMouseOverText || isOnTop && isMouseOverText)
+ container[(!isOnTop ? 'add' : 'remove') + 'Class'](CSS_TAGS_ON_TOP);
+ };
+
+ /**
+ * Returns all tag HTML elements.
+ *
+ * @signature TextExtTags.tagElements()
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.tagElements
+ */
+ p.tagElements = function()
+ {
+ return this.containerElement().find(CSS_DOT_TAG);
+ };
+
+ /**
+ * Wrapper around the `isTagAllowed` event which triggers it and returns `true`
+ * if `result` property of the second argument remains `true`.
+ *
+ * @signature TextExtTags.isTagAllowed(tag)
+ *
+ * @param tag {Object} Tag object that the current `ItemManager` can understand.
+ * Default is `String`.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.isTagAllowed
+ */
+ p.isTagAllowed = function(tag)
+ {
+ var opts = { tag : tag, result : true };
+ this.trigger(EVENT_IS_TAG_ALLOWED, opts);
+ return opts.result === true;
+ };
+
+ /**
+ * Adds specified tags to the tag list. Triggers `isTagAllowed` event for each tag
+ * to insure that it could be added. Calls `TextExt.getFormData()` to refresh the data.
+ *
+ * @signature TextExtTags.addTags(tags)
+ *
+ * @param tags {Array} List of tags that current `ItemManager` can understand. Default
+ * is `String`.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.addTags
+ */
+ p.addTags = function(tags)
+ {
+ if(!tags || tags.length == 0)
+ return;
+
+ var self = this,
+ core = self.core(),
+ container = self.containerElement(),
+ i, tag
+ ;
+
+ for(i = 0; i < tags.length; i++)
+ {
+ tag = tags[i];
+
+ if(tag && self.isTagAllowed(tag))
+ container.append(self.renderTag(tag));
+ }
+
+ self.updateFormCache();
+ core.getFormData();
+ core.invalidateBounds();
+ };
+
+ /**
+ * Returns HTML element for the specified tag.
+ *
+ * @signature TextExtTags.getTagElement(tag)
+ *
+ * @param tag {Object} Tag object in the format that current `ItemManager` can understand.
+ * Default is `String`.
+
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.getTagElement
+ */
+ p.getTagElement = function(tag)
+ {
+ var self = this,
+ list = self.tagElements(),
+ i, item
+ ;
+
+ for(i = 0; i < list.length; i++) {
+ item = $(list[i]);
+ if(self.itemManager().compareItems(item.data(CSS_TAG), tag))
+ return item;
+ }
+
+ return null;
+ };
+
+ /**
+ * Removes specified tag from the list. Calls `TextExt.getFormData()` to refresh the data.
+ *
+ * @signature TextExtTags.removeTag(tag)
+ *
+ * @param tag {Object} Tag object in the format that current `ItemManager` can understand.
+ * Default is `String`.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.removeTag
+ */
+ p.removeTag = function(tag)
+ {
+ var self = this,
+ core = self.core(),
+ element
+ ;
+
+ if(tag instanceof $)
+ {
+ element = tag;
+ tag = tag.data(CSS_TAG);
+ }
+ else
+ {
+ element = self.getTagElement(tag);
+ if (element === null) {
+ //Tag does not exist
+ return;
+ }
+ }
+
+ element.remove();
+ self.updateFormCache();
+ core.getFormData();
+ core.invalidateBounds();
+ };
+
+ /**
+ * Creates and returns new HTML element from the source code specified in the `html.tag` option.
+ *
+ * @signature TextExtTags.renderTag(tag)
+ *
+ * @param tag {Object} Tag object in the format that current `ItemManager` can understand.
+ * Default is `String`.
+ *
+ * @author agorbatchev
+ * @date 2011/08/19
+ * @id TextExtTags.renderTag
+ */
+ p.renderTag = function(tag)
+ {
+ var self = this,
+ node = $(self.opts(OPT_HTML_TAG))
+ ;
+
+ node.find('.text-label').text(self.itemManager().itemToString(tag));
+ node.data(CSS_TAG, tag);
+ return node;
+ };
+})(jQuery);
diff --git a/src/main/webapp/js/jquery-textext-master/src/stylus/_common.styl b/src/main/webapp/js/jquery-textext-master/src/stylus/_common.styl
new file mode 100644
index 0000000..4f2f095
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/stylus/_common.styl
@@ -0,0 +1,20 @@
+$close = 'close.png'
+$border = #9DACCC
+$selected = #6D84B4
+$prefix = 'text-'
+$font = 11px "lucida grande",tahoma,verdana,arial,sans-serif
+
+vendor(prop, args)
+ -webkit-{prop} : args
+ -moz-{prop} : args
+ {prop} : args
+
+border_box()
+ vendor('box-sizing', border-box)
+
+shadow(args...)
+ vendor('box-shadow', args)
+
+round_corners(args...)
+ vendor('border-radius', args)
+
diff --git a/src/main/webapp/js/jquery-textext-master/src/stylus/textext.core.styl b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.core.styl
new file mode 100644
index 0000000..265ae39
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.core.styl
@@ -0,0 +1,26 @@
+@import '_common'
+
+.{$prefix}core
+ position : relative
+
+ .{$prefix}wrap
+ position : absolute
+ background : #fff
+
+ textarea, input
+ border_box()
+ round_corners(0px)
+ border : 1px solid $border
+ outline : none
+ resize : none
+ position : absolute
+ z-index : 1
+ background : none
+ overflow : hidden
+ margin : 0
+ padding : 3px 5px 4px 5px
+ white-space : nowrap
+ font : $font
+ line-height : 13px
+ height : auto
+
diff --git a/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.arrow.styl b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.arrow.styl
new file mode 100644
index 0000000..8ece444
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.arrow.styl
@@ -0,0 +1,14 @@
+@import '_common'
+
+.{$prefix}core .{$prefix}wrap
+ .{$prefix}arrow
+ border_box()
+ position : absolute
+ top : 0
+ right : 0
+ width : 22px
+ height : 22px
+ background : url(arrow.png) 50% 50% no-repeat;
+ cursor : pointer
+ z-index : 2
+
diff --git a/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.autocomplete.styl b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.autocomplete.styl
new file mode 100644
index 0000000..4008701
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.autocomplete.styl
@@ -0,0 +1,36 @@
+@import '_common'
+
+.{$prefix}core .{$prefix}wrap .{$prefix}dropdown
+ border_box()
+ padding : 0
+ position : absolute
+ z-index : 3
+ background : #fff
+ border : 1px solid $border
+ width : 100%
+ max-height : 100px
+ padding : 1px
+ font : $font
+ display : none
+ overflow-x : hidden
+ overflow-y : auto
+
+ &.{$prefix}position-below
+ margin-top: 1px;
+
+ &.{$prefix}position-above
+ margin-bottom: 1px;
+
+ .{$prefix}list
+ .{$prefix}suggestion
+ padding : 3px 5px
+ cursor : pointer
+
+ em
+ font-style : normal
+ text-decoration : underline
+
+ &.{$prefix}selected
+ color : #fff
+ background : $selected
+
diff --git a/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.focus.styl b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.focus.styl
new file mode 100644
index 0000000..64f894f
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.focus.styl
@@ -0,0 +1,13 @@
+@import '_common'
+
+.{$prefix}core .{$prefix}wrap
+ .{$prefix}focus
+ vendor('box-shadow', 0px 0px 6px $selected)
+ position : absolute
+ width : 100%
+ height : 100%
+ display : none
+
+ &.{$prefix}show-focus
+ display : block
+
diff --git a/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.prompt.styl b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.prompt.styl
new file mode 100644
index 0000000..89bf2de
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.prompt.styl
@@ -0,0 +1,17 @@
+@import '_common'
+
+.{$prefix}core .{$prefix}wrap
+ .{$prefix}prompt
+ border_box()
+ position : absolute
+ width : 100%
+ height : 100%
+ margin : 1px 0 0 2px
+ font : $font
+ color : silver
+ overflow : hidden
+ white-space : pre
+
+ &.{$prefix}hide-prompt
+ display : none
+
diff --git a/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.tags.styl b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.tags.styl
new file mode 100644
index 0000000..a0d72e3
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/src/stylus/textext.plugin.tags.styl
@@ -0,0 +1,45 @@
+@import '_common'
+
+.{$prefix}core .{$prefix}wrap .{$prefix}tags
+ border_box()
+ position : absolute
+ width : 100%
+ height : 100%
+ padding : 3px 35px 3px 3px
+ cursor : text
+
+ &.{$prefix}tags-on-top
+ z-index : 2
+
+ .{$prefix}tag
+ float : left
+
+ .{$prefix}button
+ round_corners(2px)
+ border_box()
+ position : relative
+ float : left
+ border : 1px solid #9DACCC
+ background : #E2E6F0
+ color : #000
+ padding : 0px 17px 0px 3px
+ margin : 0 2px 2px 0
+ cursor : pointer
+ height : 16px
+ font : $font
+
+ a.{$prefix}remove
+ position : absolute
+ right : 3px
+ top : 2px
+ display : block
+ width : 11px
+ height : 11px
+ background : url($close) 0 0 no-repeat
+
+ &:hover
+ background-position: 0 -11px
+
+ &:active
+ background-position: 0 -22px
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/common.js b/src/main/webapp/js/jquery-textext-master/tests/common.js
new file mode 100644
index 0000000..b02a576
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/common.js
@@ -0,0 +1,416 @@
+var soda = require('soda');
+
+var prefix = 'css=.text-core > .text-wrap > ',
+ focus = prefix + '.text-focus',
+ textarea = prefix + 'textarea',
+ dropdown = prefix + '.text-dropdown',
+ prompt = prefix + '.text-prompt'
+ ;
+
+var DOWN = 40,
+ UP = 38,
+ ESC = 27,
+ ENTER = 13
+ ;
+
+function log(cmd, args)
+{
+ args = Array.prototype.slice.apply(arguments);
+ cmd = args.shift();
+ console.log(' \x1b[33m%s\x1b[0m%s', cmd, args.length > 0 ? ': ' + args.join(', ') : '');
+};
+
+function echo(msg)
+{
+ log('echo', msg);
+};
+
+function verifyTextExt(browser)
+{
+ browser.assertElementPresent(textarea);
+};
+
+function keyPress(charCode)
+{
+ return function(browser)
+ {
+ browser
+ .keyDown(textarea, '\\' + charCode)
+ .keyUp(textarea, '\\' + charCode)
+ ;
+ };
+};
+
+function backspace(browser)
+{
+ browser.and(keyPress(8));
+};
+
+function tagXPath(value)
+{
+ return '//div[contains(@class, "text-core")]//div[contains(@class, "text-tags")]//span[text()="' + value + '"]/../..';
+};
+
+function suggestionsXPath(selected, index)
+{
+ index = index != null ? '[' + (index + 1) + ']' : '';
+ selected = selected == true ? '[contains(@class, "text-selected")]' : '';
+
+ return '//div[contains(@class, "text-core")]//div[contains(@class, "text-dropdown")]//div[contains(@class, "text-suggestion")]' + index + selected;
+};
+
+function assertSuggestionItem(test)
+{
+ return function(browser) { browser.assertVisible(suggestionsXPath() + '//span[text()="Basic"]') };
+};
+
+function assertOutput(value)
+{
+ return function(browser) { browser.assertElementPresent('//pre[@id="output"][contains(text(), \'' + value + '\')]') };
+};
+
+function assertNotOutput(value)
+{
+ return function(browser) { browser.assertElementNotPresent('//pre[@id="output"][contains(text(), \'' + value + '\')]') };
+};
+
+function assertTagPresent(value)
+{
+ return function(browser) { browser.assertElementPresent(tagXPath(value)) };
+};
+
+function assertTagNotPresent(value)
+{
+ return function(browser) { browser.assertElementNotPresent(tagXPath(value)) };
+};
+
+function enterKey(browser)
+{
+ browser.and(keyPress(13));
+};
+
+function typeTag(value)
+{
+ return function(browser)
+ {
+ browser
+ .type(textarea, '')
+ .typeKeys(textarea, value)
+ .and(enterKey)
+ ;
+ };
+};
+
+function clearInput(browser)
+{
+ browser
+ .and(keyPress(27))
+ .type(textarea, '')
+ ;
+};
+
+function focusInput(browser)
+{
+ browser.fireEvent(textarea, 'focus');
+};
+
+function defaultWrap(value)
+{
+ return value;
+};
+
+function typeAndValidateTag(value, wrap)
+{
+ wrap = wrap || defaultWrap;
+ return function(browser)
+ {
+ browser
+ .and(typeTag(value))
+ .and(assertTagPresent(wrap(value)))
+ ;
+ };
+};
+
+function closeTag(value, wrap)
+{
+ wrap = wrap || defaultWrap;
+ return function(browser)
+ {
+ browser
+ .click(tagXPath(wrap(value)) + '//a[@class="text-remove"]')
+ .and(assertTagNotPresent(wrap(value)))
+ ;
+ };
+};
+
+function screenshot(name)
+{
+ return function(browser)
+ {
+ // browser.captureEntirePageScreenshot(__dirname + '/' + name + ' (' + (new Date().toUTCString().replace(/:/g, '.')) + ').png');
+ };
+};
+
+function createBrowser()
+{
+ return soda.createClient({
+ host : 'localhost',
+ port : 4444,
+ url : 'http://localhost:4000',
+ browser : 'firefox'
+ });
+};
+
+function testAjaxFunctionality()
+{
+ return function(browser)
+ {
+ browser
+ .and(focusInput)
+ .typeKeys(textarea, 'ba')
+ .waitForVisible(dropdown)
+ .and(assertSuggestionItem('Basic'))
+ ;
+ }
+};
+
+function testArrowFunctionality()
+{
+ var arrow = prefix + '.text-arrow';
+
+ return function(browser)
+ {
+ browser
+ // open/close test
+ .click(arrow)
+ .waitForVisible(dropdown)
+ .click(arrow)
+ .waitForNotVisible(dropdown)
+
+ // open and click on item
+ .click(arrow)
+ .waitForVisible(dropdown)
+ .click(suggestionsXPath(false, 0))
+ .waitForNotVisible(dropdown)
+ .and(assertOutput('Basic'))
+ .assertValue(textarea, 'Basic')
+ .assertNotVisible(prompt)
+ ;
+ };
+};
+
+function testFilterFunctionality()
+{
+ return function(browser)
+ {
+ browser
+ .and(focusInput)
+
+ .and(typeTag('hello'))
+ .and(assertTagNotPresent('hello'))
+ .and(assertNotOutput('hello'))
+
+ .and(typeTag('world'))
+ .and(assertTagNotPresent('world'))
+ .and(assertNotOutput('world'))
+ ;
+ };
+};
+
+function testTagFunctionality(opts)
+{
+ opts = opts || {};
+
+ var labelWrap = opts.label,
+ objectWrap = opts.object
+ ;
+
+ function output()
+ {
+ var list = Array.prototype.slice.apply(arguments);
+
+ if(objectWrap)
+ for(var i = 0; i < list.length; i++)
+ list[i] = objectWrap(list[i]);
+
+ var match = JSON.stringify(list).replace(/^\[|\]$/g, '');
+
+ return assertOutput(match);
+ };
+
+ return function(browser)
+ {
+ browser
+ .and(focusInput)
+
+ .and(typeAndValidateTag('hello', labelWrap))
+ .and(output('hello'))
+
+ .and(typeAndValidateTag('world', labelWrap))
+ .and(output('hello','world'))
+
+ .and(typeAndValidateTag('word1', labelWrap))
+ .and(output('hello','world','word1'))
+
+ .and(typeAndValidateTag('word2', labelWrap))
+ .and(output('hello','world','word1','word2'))
+
+ .and(typeAndValidateTag('word3', labelWrap))
+ .and(output('hello','world','word1','word2','word3'))
+
+ .and(closeTag('word2', labelWrap))
+ .and(output('hello','world','word1','word3'))
+
+ .and(closeTag('word1', labelWrap))
+ .and(output('hello','world','word3'))
+
+ .and(closeTag('word3', labelWrap))
+ .and(output('hello','world'))
+
+ // backspace
+ .and(backspace)
+ .and(assertTagNotPresent('world'))
+ ;
+ };
+};
+
+function testPromptFunctionality(secondary)
+{
+ return function(browser)
+ {
+ browser
+ .assertVisible(prompt)
+ .and(focusInput)
+ .and(secondary)
+ .assertNotVisible(prompt)
+ ;
+ };
+};
+
+function testAutocompleteFunctionality(finalAssert)
+{
+ finalAssert = finalAssert || function(browser)
+ {
+ browser
+ .assertValue(textarea, 'OCAML')
+ .and(assertOutput('OCAML'))
+ ;
+ };
+
+ return function(browser)
+ {
+ browser
+ .click(textarea)
+ .and(clearInput)
+
+ // activate the dropdown
+ .and(keyPress(DOWN))
+ .assertVisible(dropdown)
+ .assertVisible(suggestionsXPath(true, 0))
+
+ // go to the second item
+ .and(keyPress(DOWN))
+ .assertElementNotPresent(suggestionsXPath(true, 0))
+ .assertVisible(suggestionsXPath(true, 1))
+
+ // go to the third item
+ .and(keyPress(DOWN))
+ .assertElementNotPresent(suggestionsXPath(true, 1))
+ .assertVisible(suggestionsXPath(true, 2))
+
+ // go back up to the second item
+ .and(keyPress(UP))
+ .assertElementNotPresent(suggestionsXPath(true, 2))
+ .assertVisible(suggestionsXPath(true, 1))
+
+ // go back up to the first item
+ .and(keyPress(UP))
+ .assertElementNotPresent(suggestionsXPath(true, 1))
+ .assertVisible(suggestionsXPath(true, 0))
+
+ // test the mouse click
+ .click(suggestionsXPath(true, 0))
+ .waitForNotVisible(dropdown)
+ .assertValue(textarea, 'Basic')
+ .and(assertOutput('Basic'))
+
+ .and(clearInput)
+ .typeKeys(textarea, 'oca')
+ .waitForVisible(dropdown)
+ .assertVisible(suggestionsXPath(true, 0))
+ .and(enterKey)
+ .assertNotVisible(dropdown)
+
+ .and(finalAssert)
+ ;
+ };
+};
+
+function testPlainInputFunctionality()
+{
+ return function(browser)
+ {
+ browser
+ .typeKeys(textarea, 'Hello world')
+ // for some reason without the enter key last letter doesn't trigger events...
+ // pressing the enter key puts the last letter through... odd??
+ .and(enterKey)
+ .and(assertOutput('"Hello world"'))
+ ;
+ };
+};
+
+function runModule(run)
+{
+ var browser = createBrowser();
+
+ browser.on('command', log);
+
+ browser.chain.session()
+ .windowMaximize()
+ .and(run)
+ .testComplete()
+ .end(function(err)
+ {
+ if (err) throw err;
+ echo('ALL DONE');
+ })
+ ;
+};
+
+module.exports = {
+ log : log,
+ echo : echo,
+ clearInput : clearInput,
+ focusInput : focusInput,
+ backspace : backspace,
+ keyPress : keyPress,
+ verifyTextExt : verifyTextExt,
+ tagXPath : tagXPath,
+ suggestionsXPath : suggestionsXPath,
+ assertTagPresent : assertTagPresent,
+ assertTagNotPresent : assertTagNotPresent,
+ assertOutput : assertOutput,
+ assertSuggestionItem : assertSuggestionItem,
+ typeTag : typeTag,
+ typeAndValidateTag : typeAndValidateTag,
+ enterKey : enterKey,
+ closeTag : closeTag,
+ screenshot : screenshot,
+ createBrowser : createBrowser,
+ runModule : runModule,
+ testFilterFunctionality : testFilterFunctionality,
+ testTagFunctionality : testTagFunctionality,
+ testPromptFunctionality : testPromptFunctionality,
+ testAutocompleteFunctionality : testAutocompleteFunctionality,
+ testPlainInputFunctionality : testPlainInputFunctionality,
+ testAjaxFunctionality : testAjaxFunctionality,
+ testArrowFunctionality : testArrowFunctionality,
+
+ css : {
+ focus : focus,
+ textarea : textarea,
+ dropdown : dropdown
+ }
+};
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/.gitignore b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/.gitignore
new file mode 100644
index 0000000..241e560
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/.gitignore
@@ -0,0 +1,2 @@
+*
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/.parentlock b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/.parentlock
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/compatibility.ini b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/compatibility.ini
new file mode 100644
index 0000000..acf9f1e
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/compatibility.ini
@@ -0,0 +1,7 @@
+[Compatibility]
+LastVersion=9.0.1_20111220165912/20111220165912
+LastOSABI=Darwin_x86_64-gcc3
+LastPlatformDir=/Applications/Firefox 9.0.1.app/Contents/MacOS
+LastAppDir=/Applications/Firefox 9.0.1.app/Contents/MacOS
+
+InvalidateCaches=1
diff --git a/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/extensions.ini b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/extensions.ini
new file mode 100644
index 0000000..846a2af
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/extensions.ini
@@ -0,0 +1,4 @@
+[ExtensionDirs]
+
+[ThemeDirs]
+Extension0=/Applications/Firefox 9.0.1.app/Contents/MacOS/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}
diff --git a/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/localstore.rdf b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/localstore.rdf
new file mode 100644
index 0000000..5bcf788
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/localstore.rdf
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/pluginreg.dat b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/pluginreg.dat
new file mode 100644
index 0000000..b604682
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/pluginreg.dat
@@ -0,0 +1,190 @@
+Generated File. Do not edit.
+
+[HEADER]
+Version:0.15:$
+Arch:x86_64-gcc3:$
+
+[PLUGINS]
+net.juniper.DSSafariExtensions.plugin:$
+/Library/Internet Plug-Ins/net.juniper.DSSafariExtensions.plugin:$
+19243:$
+1321897505000:0:1:$
+Juniper Networks Safari Extensions:$
+Juniper Networks Safari Extensions:$
+1
+0:application/x-net-juniper-dssafariextensions:Juniper Networks Extension Type::$
+AmazonMP3DownloaderPlugin.plugin:$
+/Applications/Amazon MP3 Downloader.app/Contents/Resources/AmazonMP3DownloaderPlugin.plugin:$
+AmazonMP3DownloaderPlugin 1.0.15:$
+1321560836000:0:1:$
+AmazonMP3DownloaderPlugin 1.0.15:$
+AmazonMP3DownloaderPlugin:$
+1
+0:application/x-amz:AmazonMP3DownloaderPlugin 1.0.15::$
+Flash Player.plugin:$
+/Library/Internet Plug-Ins/Flash Player.plugin:$
+10.3.181.14:$
+1310162668000:0:1:$
+Shockwave Flash 10.3 r181:$
+Shockwave Flash:$
+2
+0:application/x-shockwave-flash:Shockwave Flash:swf:$
+1:application/futuresplash:FutureSplash Player:spl:$
+npgtpo3dautoplugin.plugin:$
+/Library/Internet Plug-Ins/npgtpo3dautoplugin.plugin:$
+0.1.44.5:$
+1308958693000:0:1:$
+Google Talk Plugin Video Accelerator version:0.1.44.5:$
+Google Talk Plugin Video Accelerator:$
+1
+0:application/vnd.gtpo3d.auto:Google Talk Plugin Video Accelerator Type::$
+googletalkbrowserplugin.plugin:$
+/Library/Internet Plug-Ins/googletalkbrowserplugin.plugin:$
+2.1.7.2968:$
+1308958693000:0:1:$
+Version 2.1.7.2968:$
+Google Talk NPAPI Plugin:$
+1
+0:application/googletalk:Google voice and video chat:googletalk:$
+QuickTime Plugin.plugin:$
+/Library/Internet Plug-Ins/QuickTime Plugin.plugin:$
+7.7.1:$
+1308549020000:0:1:$
+The QuickTime Plugin allows you to view a wide variety of multimedia content in web pages. For more information, visit the QuickTime Web site.:$
+QuickTime Plug-in 7.7.1:$
+59
+0:audio/x-midi:MIDI:mid,midi,smf,kar:$
+1:audio/x-ac3:AC3 audio:ac3:$
+2:video/mp4:MPEG-4 media:mp4:$
+3:video/x-m4v:Video:m4v:$
+4:application/x-sdp:SDP stream descriptor:sdp:$
+5:audio/AMR:AMR audio:AMR:$
+6:application/x-rtsp:RTSP stream descriptor:rtsp,rts:$
+7:image/tiff:TIFF image:tif,tiff:$
+8:video/3gpp2:3GPP2 media:3g2,3gp2:$
+9:image/jpeg2000:JPEG2000 image:jp2:$
+10:audio/mpeg:MPEG audio:mpeg,mpg,m1s,m1a,mp2,mpm,mpa,m2a,mp3,swa:$
+11:image/x-quicktime:QuickTime image:qtif,qti:$
+12:audio/mpeg3:MP3 audio:mp3,swa:$
+13:application/sdp:SDP stream descriptor:sdp:$
+14:application/x-mpeg:AMC media:amc:$
+15:image/x-macpaint:MacPaint image:pntg,pnt,mac:$
+16:video/mpeg:MPEG media:mpeg,mpg,m1s,m1v,m1a,m75,m15,mp2,mpm,mpv,mpa:$
+17:video/x-msvideo:Video For Windows:avi,vfw:$
+18:audio/aac:AAC audio:aac,adts:$
+19:audio/x-gsm:GSM audio:gsm:$
+20:video/sd-video:SD video:sdv:$
+21:audio/x-caf:CAF audio:caf:$
+22:image/x-jpeg2000-image:JPEG2000 image:jp2:$
+23:audio/3gpp:3GPP media:3gp,3gpp:$
+24:audio/mp3:MP3 audio:mp3,swa:$
+25:image/x-targa:TGA image:targa,tga:$
+26:video/avi:Video For Windows:avi,vfw:$
+27:image/png:PNG image:png:$
+28:image/x-tiff:TIFF image:tif,tiff:$
+29:audio/mp4:MPEG-4 media:mp4:$
+30:image/jp2:JPEG2000 image:jp2:$
+31:audio/x-aiff:AIFF audio:aiff,aif,aifc,cdda:$
+32:video/x-mpeg:MPEG media:mpeg,mpg,m1s,m1v,m1a,m75,m15,mp2,mpm,mpv,mpa:$
+33:video/3gpp:3GPP media:3gp,3gpp:$
+34:audio/x-mpeg:MPEG audio:mpeg,mpg,m1s,m1a,mp2,mpm,mpa,m2a,mp3,swa:$
+35:audio/x-aac:AAC audio:aac,adts:$
+36:audio/mid:MIDI:mid,midi,smf,kar:$
+37:audio/3gpp2:3GPP2 media:3g2,3gp2:$
+38:audio/midi:MIDI:mid,midi,smf,kar:$
+39:audio/x-wav:WAVE audio:wav,bwf:$
+40:audio/x-mp3:MP3 audio:mp3,swa:$
+41:video/quicktime:QuickTime Movie:mov,qt,mqv:$
+42:audio/x-mpeg3:MP3 audio:mp3,swa:$
+43:image/x-bmp:BMP image:bmp,dib:$
+44:audio/ac3:AC3 audio:ac3:$
+45:image/pict:PICT image:pict,pic,pct:$
+46:audio/x-m4p:AAC audio:m4p:$
+47:audio/x-m4a:AAC audio:m4a:$
+48:image/x-png:PNG image:png:$
+49:image/x-pict:PICT image:pict,pic,pct:$
+50:image/x-sgi:SGI image:sgi,rgb:$
+51:audio/x-m4b:AAC audio book:m4b:$
+52:video/flc:AutoDesk Animator:flc,fli,cel:$
+53:audio/aiff:AIFF audio:aiff,aif,aifc,cdda:$
+54:video/msvideo:Video For Windows:avi,vfw:$
+55:audio/basic:uLaw/AU audio:au,snd,ulw:$
+56:image/jpeg2000-image:JPEG2000 image:jp2:$
+57:audio/wav:WAVE audio:wav,bwf:$
+58:audio/vnd.qcelp:QUALCOMM PureVoice audio:qcp,qcp:$
+JavaAppletPlugin.plugin:$
+/System/Library/Java/Support/CoreDeploy.bundle/Contents/JavaAppletPlugin.plugin:$
+14.1.0:$
+1308272440000:0:1:$
+Displays Java applet content, or a placeholder if Java is not installed.:$
+Java Applet Plug-in:$
+17
+0:application/x-java-applet;version=1.1.3:Java applet::$
+1:application/x-java-applet:Basic Java Applets:javaapplet:$
+2:application/x-java-applet;version=1.2.2:Java applet::$
+3:application/x-java-applet;version=1.5:Java applet::$
+4:application/x-java-vm:Java applet::$
+5:application/x-java-applet;version=1.3.1:Java applet::$
+6:application/x-java-applet;version=1.3:Java applet::$
+7:application/x-java-applet;version=1.1.2:Java applet::$
+8:application/x-java-applet;version=1.1:Java applet::$
+9:application/x-java-applet;version=1.2.1:Java applet::$
+10:application/x-java-applet;version=1.6:Java applet::$
+11:application/x-java-applet;version=1.4.2:Java applet::$
+12:application/x-java-applet;version=1.4:Java applet::$
+13:application/x-java-applet;version=1.1.1:Java applet::$
+14:application/x-java-applet;version=1.2:Java applet::$
+15:application/x-java-applet;jpi-version=1.6.0_29:Java applet::$
+16:application/x-java-vm-npruntime:::$
+WebEx64.plugin:$
+/Users/agorbatchev/Library/Internet Plug-Ins/WebEx64.plugin:$
+1.0:$
+1307651021000:0:1:$
+WebEx64 General Plugin Container Version 202:$
+WebEx64 General Plugin Container:$
+1
+0:application/webx-gpc-plugin64:gpc::$
+Silverlight.plugin:$
+/Library/Internet Plug-Ins/Silverlight.plugin:$
+4.0.60129.0:$
+1296448496000:0:1:$
+4.0.60129.0:$
+Silverlight Plug-In:$
+2
+0:application/x-silverlight:Microsoft Silverlight:xaml:$
+1:application/x-silverlight-2:Microsoft Silverlight:xaml:$
+DirectorShockwave.plugin:$
+/Library/Internet Plug-Ins/DirectorShockwave.plugin:$
+11.5.7r609:$
+1272547663000:0:1:$
+:$
+:$
+0
+iPhotoPhotocast.plugin:$
+/Library/Internet Plug-Ins/iPhotoPhotocast.plugin:$
+7.0:$
+1258508974000:0:1:$
+iPhoto6:$
+iPhotoPhotocast:$
+1
+0:application/photo:iPhoto 700::$
+OfficeLiveBrowserPlugin.plugin:$
+/Library/Internet Plug-Ins/OfficeLiveBrowserPlugin.plugin:$
+12.2.5:$
+1256000273000:0:1:$
+Office Live Update v1.0:$
+Microsoft Office Live Plug-in:$
+1
+0:application/officelive:Office Live Update v1.0::$
+
+[INVALID]
+/Library/Internet Plug-Ins/Quartz Composer.webplugin:$
+1308270108000:$
+/Library/Internet Plug-Ins/nsIQTScriptablePlugin.xpt:$
+1321292803000:$
+/Library/Internet Plug-Ins/flashplayer.xpt:$
+1304635372000:$
+/Library/Internet Plug-Ins/Disabled Plug-Ins:$
+1274731912000:$
+/Library/Internet Plug-Ins/AdobePDFViewer.plugin:$
+1274910092000:$
diff --git a/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/prefs.js b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/prefs.js
new file mode 100644
index 0000000..f80ed5a
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/prefs.js
@@ -0,0 +1,41 @@
+// Mozilla User Preferences
+
+/* Do not edit this file.
+ *
+ * If you make changes to this file while the application is running,
+ * the changes will be overwritten when the application exits.
+ *
+ * To make a manual change to preferences, you can visit the URL about:config
+ * For more information, see http://www.mozilla.org/unix/customizing.html#prefs
+ */
+
+user_pref("browser.bookmarks.restore_default_bookmarks", false);
+user_pref("browser.cache.disk.capacity", 1048576);
+user_pref("browser.cache.disk.smart_size.first_run", false);
+user_pref("browser.cache.disk.smart_size_cached_value", 1048576);
+user_pref("browser.migration.version", 5);
+user_pref("browser.places.smartBookmarksVersion", 2);
+user_pref("browser.rights.3.shown", true);
+user_pref("browser.shell.checkDefaultBrowser", false);
+user_pref("browser.startup.homepage_override.buildID", "20111220165912");
+user_pref("browser.startup.homepage_override.mstone", "rv:9.0.1");
+user_pref("extensions.blocklist.pingCountVersion", 0);
+user_pref("extensions.bootstrappedAddons", "{}");
+user_pref("extensions.databaseSchema", 6);
+user_pref("extensions.enabledAddons", "{972ce4c6-7e08-4474-a285-3208198ce6fd}:9.0.1");
+user_pref("extensions.installCache", "[{\"name\":\"app-global\",\"addons\":{\"{972ce4c6-7e08-4474-a285-3208198ce6fd}\":{\"descriptor\":\"/Applications/Firefox 9.0.1.app/Contents/MacOS/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}\",\"mtime\":1324444015000}}}]");
+user_pref("extensions.lastAppVersion", "9.0.1");
+user_pref("extensions.lastPlatformVersion", "9.0.1");
+user_pref("extensions.pendingOperations", false);
+user_pref("extensions.shownSelectionUI", true);
+user_pref("intl.charsetmenu.browser.cache", "UTF-8");
+user_pref("network.cookie.prefsMigrated", true);
+user_pref("places.history.expiration.transient_current_max_pages", 104858);
+user_pref("places.history.expiration.transient_optimal_database_size", 167772160);
+user_pref("privacy.cpd.siteSettings", true);
+user_pref("privacy.sanitize.migrateFx3Prefs", true);
+user_pref("privacy.sanitize.timeSpan", 0);
+user_pref("toolkit.telemetry.prompted", 2);
+user_pref("urlclassifier.keyupdatetime.https://sb-ssl.google.com/safebrowsing/newkey", 1327691121);
+user_pref("xpinstall.whitelist.add", "");
+user_pref("xpinstall.whitelist.add.36", "");
diff --git a/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/search.json b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/search.json
new file mode 100644
index 0000000..1a94704
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/search.json
@@ -0,0 +1 @@
+{"version":7,"buildID":"20111220165912","locale":"en-US","directories":{"/Applications/Firefox 9.0.1.app/Contents/MacOS/searchplugins":{"lastModifiedTime":1324444016000,"engines":[{"_id":"[app]/amazondotcom.xml","_name":"Amazon.com","_hidden":false,"description":"Amazon.com Search","__searchForm":"http://www.amazon.com/","_iconURL":"data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHgSURBVHjalFM9TNtQEP4cB7PwM1RITUXIgsRaYEEVEyKZwhiyZAQyd0BhpFOlIjoBqhjSqVQMoVMLLAjEwECCQJkSkBqJYDOAFOMKFSf28d7DTUxiUDnp/Pzeu/vuu7t3ICKF6SLTMv2/lB0fRWKfjwDm4JJisYh0Oo3fpZLYT0SjSCQS8JAFMADNDZ3NZsnf1taiqVTKi4nGASruk5lkkmTmMB6JUKFQqO+DfX1eABWeQoVR6f7HSdM0obqu48Yw8G1tDT82NsRd1TSbU9BbGPCog8PDj+jLzurFoAVgMh4XxoNDQ6SqKi0tL9eBvAB8zZwymYxYY7EYAoEA8vm82BNTg6XUIs0MeGTZoR1mhXSnwNl4pmAbjU7mcjkKhkL1ynMnntZ4OEw3VyrV8utk7s5TdW++0QXz+1i3P7IK36t+PCfVn1OQOoOA0gXr5DPak+cPXbBK+/T3S69AtY3LJ98vZ1or/iLr+pTuvr59/A6s003UdqZFJF/PCKQ3o5CUznoBST2AfbEF/9iqYEDaIfwj73VJPEfgNTe0tWNYR0uwy9uOW0OkrgHI7z5ADo2C7v48nLV3XHKAT+x/1m1sX58xsBxg8rZJrDYD8DHHp4aJj/MK09sXjPOt46PcCzAACXY8/u34wN0AAAAASUVORK5CYII=","_urls":[{"template":"http://www.amazon.com/exec/obidos/external-search/","rels":[],"params":[{"name":"field-keywords","value":"{searchTerms}"},{"name":"mode","value":"blended"},{"name":"tag","value":"mozilla-20"},{"name":"sourceid","value":"Mozilla-search"}]}],"filePath":"/Applications/Firefox 9.0.1.app/Contents/MacOS/searchplugins/amazondotcom.xml"},{"_id":"[app]/bing.xml","_name":"Bing","_hidden":false,"description":"Bing. Search by Microsoft.","__searchForm":"http://www.bing.com/search","_iconURL":"data:image/x-icon;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAABMLAAATCwAAAAAAAAAAAAAVpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8ysf97zf+24//F6f/F6f/F6f+K0/9QvP8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8krP+Z2P/////////w+f/F6f/F6f/i9P/////////T7v9Bt/8Vpv8Vpv8Vpv8Vpv/T7v/////w+f97zf8Vpv8Vpv8Vpv8Vpv9QvP/T7v/////w+f9Bt/8Vpv8Vpv97zf////////9QvP8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8krP/i9P/////i9P8Vpv8Vpv+24//////i9P8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv+K0/////////8Vpv8Vpv/F6f////////8krP8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv+n3v/////w+f8Vpv8Vpv/F6f////////+n3v8krP8Vpv8Vpv8Vpv8Vpv8Vpv9tx/////////+Z2P8Vpv8Vpv/F6f/////////////i9P+K0/9QvP9QvP9tx//F6f////////+n3v8Vpv8Vpv8Vpv/F6f/////T7v+Z2P/i9P////////////////////+24/9QvP8Vpv8Vpv8Vpv8Vpv/F6f/////F6f8Vpv8Vpv8krP9QvP9QvP9Bt/8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv/F6f/////F6f8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv9Bt/9QvP9Bt/8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8Vpv8AAHBsAABhdAAAbiAAAHJ0AABsaQAAdGkAACBDAABlbgAAUEEAAEVYAAAuQwAAOy4AAEU7AABBVAAAQ00AAC5W","_urls":[{"template":"http://api.bing.com/osjson.aspx","rels":[],"type":"application/x-suggestions+json","params":[{"name":"query","value":"{searchTerms}"},{"name":"form","value":"OSDJAS"}]},{"template":"http://www.bing.com/search","rels":[],"params":[{"name":"q","value":"{searchTerms}"},{"name":"form","value":"MOZSBR"},{"pref":"ms-pc","name":"pc","condition":"pref","mozparam":true}]},{"template":"http://www.bing.com/search","rels":[],"type":"application/x-moz-keywordsearch","params":[{"name":"q","value":"{searchTerms}"},{"name":"form","value":"MOZLBR"},{"pref":"ms-pc","name":"pc","condition":"pref","mozparam":true}]}],"filePath":"/Applications/Firefox 9.0.1.app/Contents/MacOS/searchplugins/bing.xml","queryCharset":"UTF-8"},{"_id":"[app]/eBay.xml","_name":"eBay","_hidden":false,"description":"eBay - Online auctions","__searchForm":"http://search.ebay.com/","_iconURL":"data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABFUlEQVQ4jdWTvUoDQRSFvxUfQMFSyBvYpLGSSWFpncY6lsLWFiupBBtLBRsfQcQ2a782PoCkSrONlUGy5LPYn6wbu4DghcOcYs65595hIpVNamsj9V8ajOeFzgsFLmo+LxTXcWJVX8WyppIgKSVPkQQ/F0u3gSFwBfTqdoPoBYDnxRFcDgA4Z4cbPtazqblZptBgxJ2BtGydv+vbkyahSUGC0zxT7VeZ0DguBXFsRs9AKtzq/amOKA2sTAylzMDKoIM6wfXhcWmcBKd51ukeWq8Qx6V0MmFAuppxdx/OIgB6e/32+SoTUGfdHTxy0CRodtF6jZpW2R2qs/alQNrgYTytR8Cf1Rh08VuNGkECJCtd5L//TN/BEWxoE8dlIQAAAABJRU5ErkJggg==","_urls":[{"template":"http://anywhere.ebay.com/services/suggest/","rels":[],"type":"application/x-suggestions+json","params":[{"name":"s","value":"0"},{"name":"q","value":"{searchTerms}"}]},{"template":"http://rover.ebay.com/rover/1/711-47294-18009-3/4","rels":[],"params":[{"name":"mpre","value":"http://shop.ebay.com/?_nkw={searchTerms}"}]}],"filePath":"/Applications/Firefox 9.0.1.app/Contents/MacOS/searchplugins/eBay.xml"},{"_id":"[app]/google.xml","_name":"Google","_hidden":false,"description":"Google Search","__searchForm":"http://www.google.com/","_iconURL":"data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA","_urls":[{"template":"http://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}&q={searchTerms}","rels":[],"type":"application/x-suggestions+json","params":[]},{"template":"http://www.google.com/search","rels":[],"params":[{"name":"q","value":"{searchTerms}"},{"name":"ie","value":"utf-8"},{"name":"oe","value":"utf-8"},{"name":"aq","value":"t"},{"name":"rls","value":"{moz:distributionID}:{moz:locale}:{moz:official}"},{"name":"client","falseValue":"firefox","trueValue":"firefox-a","condition":"defaultEngine","mozparam":true}]}],"filePath":"/Applications/Firefox 9.0.1.app/Contents/MacOS/searchplugins/google.xml","queryCharset":"UTF-8"},{"_id":"[app]/twitter.xml","_name":"Twitter","_hidden":false,"description":"Realtime Twitter Search","__searchForm":"https://twitter.com/search/","_iconURL":"data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A/v7+D/7+/j/+/v5g/v7+YP7+/mD+/v5I/v7+KP///wD///8A////AP///wD///8A////AP///wD+/v4H/v7+UPbv4pHgx47B1K9Y3tWwWN7Ur1je3sKCx+rbuKj+/v5n/v7+GP///wD///8A////AP///wD+/v4Y+fbweM2ycMe2iB7/vI0f/8STIf/KlyL/zJki/8yZIv/LmCL/0ahK5/Hp1JH+/v4Y////AP///wD///8A7OTTaquHN+CujkXPs5ZTv6N6G/+2iB7/xpUh/8yZIv/MmSL/zJki/8yZIv/Kmy738OjUi////wD///8A////AMKtfY7w6+Ef////AP///wD///8A3sqbp8iWIf/MmSL/zJki/8yZIv/MmSL/y5gi/8mePO7+/v4w////AP///wD///8A////AP///wD+/v4H/v7+V9CtWN3KmCL/zJki/8yZIv/MmSL/zJki/8yZIv/JlyH/5tSqp/7+/mD+/v4/////AP///wD///8A+PXvJtGyZdXNnS/3y5gi/8qYIv/LmCL/zJki/8yZIv/MmSL/y5gi/82iPO7LqVfe0byMmf///wD///8A/v7+D/Do1JHKmy73ypci/8KSIP+/jyD/xpQh/8uYIv/MmSL/zJki/8qYIv+/jyD/rIEd/9nKqH7///8A////APPu4TzAlSz3wZEg/7mLH/+sgR3/uZdGz7mLH//JlyH/zJki/8yZIv/GlSH/to0r9eXbxD/Vx6dg////AP7+/h/p38WhtIsq9al/HP+kfyjuybaKgf///wCzjzjlwJAg/8qYIv/JlyH/u4wf/8CkYrn///8A////AP///wDj2sRMnHUa/7meYa7Vx6dg////AP///wD///8A2MmnYK6DHf++jiD/vo4g/62CHf/k2sQ/////AP///wD///8A8OvhH/f07w////8A////AP///wD///8A////AP///wC/p3Cfpnwc/66GKvPg1LZ8////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ANXHp2DJtoqByLWKgf///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A//8AAP//AADgPwAAwA8AAIAHAAB4BwAA+AMAAPAAAADgAQAA4AMAAMEDAADPhwAA/48AAP/nAAD//wAA//8AAA==","_urls":[{"template":"https://twitter.com/search/{searchTerms}","rels":[],"params":[{"name":"partner","value":"Firefox"},{"name":"source","value":"desktop-search"}]}],"filePath":"/Applications/Firefox 9.0.1.app/Contents/MacOS/searchplugins/twitter.xml","queryCharset":"UTF-8"},{"_id":"[app]/wikipedia.xml","_name":"Wikipedia (en)","_hidden":false,"description":"Wikipedia, the free encyclopedia","__searchForm":"http://en.wikipedia.org/wiki/Special:Search","_iconURL":"data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAEAgQAhIOEAMjHyABIR0gA6ejpAGlqaQCpqKkAKCgoAPz9%2FAAZGBkAmJiYANjZ2ABXWFcAent6ALm6uQA8OjwAiIiIiIiIiIiIiI4oiL6IiIiIgzuIV4iIiIhndo53KIiIiB%2FWvXoYiIiIfEZfWBSIiIEGi%2FfoqoiIgzuL84i9iIjpGIoMiEHoiMkos3FojmiLlUipYliEWIF%2BiDe0GoRa7D6GPbjcu1yIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","_urls":[{"template":"http://en.wikipedia.org/w/api.php","rels":[],"type":"application/x-suggestions+json","params":[{"name":"action","value":"opensearch"},{"name":"search","value":"{searchTerms}"}]},{"template":"http://en.wikipedia.org/wiki/Special:Search","rels":[],"params":[{"name":"search","value":"{searchTerms}"},{"name":"sourceid","value":"Mozilla-search"}]}],"filePath":"/Applications/Firefox 9.0.1.app/Contents/MacOS/searchplugins/wikipedia.xml","queryCharset":"UTF-8"},{"_id":"[app]/yahoo.xml","_name":"Yahoo","_hidden":false,"description":"Yahoo Search","__searchForm":"http://search.yahoo.com/","_iconURL":"data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbgJqAIoCdgCaAnoAnhKCAKYijgCuLpIAskKeALpSpgC+Yq4AzHy8ANqezgDmvt4A7tLqAPz5+wD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKlRFIoABWAKERERE6ADcKMzzu2hOgAAhERK8REWCWBERE36ERMHMEREvo6iEgY6hEn6Pu0mAzqkz/xjMzoDNwpERERDoAMzAKlERIoAAzMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA//8AAP//AADAOQAAgBkAAAAPAAAACQAAAAkAAAAIAAAACAAAAAgAAIAYAADAOAAA//8AAP//AAD//wAA","_urls":[{"template":"http://ff.search.yahoo.com/gossip?output=fxjson&command={searchTerms}","rels":[],"type":"application/x-suggestions+json","params":[]},{"template":"http://search.yahoo.com/search","rels":[],"params":[{"name":"p","value":"{searchTerms}"},{"name":"ei","value":"UTF-8"},{"pref":"yahoo-fr","name":"fr","condition":"pref","mozparam":true}]}],"filePath":"/Applications/Firefox 9.0.1.app/Contents/MacOS/searchplugins/yahoo.xml","queryCharset":"UTF-8"}]}}}
\ No newline at end of file
diff --git a/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/sessionstore.js1 b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/sessionstore.js1
new file mode 100644
index 0000000..ac63c35
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/sessionstore.js1
@@ -0,0 +1 @@
+{"windows":[{"tabs":[{"entries":[{"url":"about:home","title":"Mozilla Firefox Start Page","ID":0,"docshellID":5,"owner_b64":"NhAra3tiRRqhyKDUVsktxQAAAAAAAAAAwAAAAAAAAEYAAQAAAAAAAS8nfAAOr03buTZBMmukiq45X+BFfRhK26P9r5jIoa8RAAAAAAVhYm91dAAAAARob21lAODaHXAvexHTjNAAYLD8FKM5X+BFfRhK26P9r5jIoa8RAAAAAA5tb3otc2FmZS1hYm91dAAAAARob21lAAAAAA==","docIdentifier":0,"formdata":{},"scroll":"0,0"}],"index":1,"hidden":false,"attributes":{"image":"chrome://branding/content/icon16.png"}}],"selected":1,"_closedTabs":[],"busy":false,"width":1260,"height":1404,"screenX":211,"screenY":22,"sizemode":"normal"}],"selectedWindow":1,"_closedWindows":[],"session":{"state":"stopped","lastUpdate":1325099214574,"startTime":1325099208545}}
\ No newline at end of file
diff --git a/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/urlclassifier.pset b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/urlclassifier.pset
new file mode 100644
index 0000000..a210985
Binary files /dev/null and b/src/main/webapp/js/jquery-textext-master/tests/firefox_profile/urlclassifier.pset differ
diff --git a/src/main/webapp/js/jquery-textext-master/tests/get_selenium_rc b/src/main/webapp/js/jquery-textext-master/tests/get_selenium_rc
new file mode 100644
index 0000000..be88b26
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/get_selenium_rc
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+curl -O "http://selenium.googlecode.com/files/selenium-server-standalone-2.15.0.jar"
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/start_selenium_rc b/src/main/webapp/js/jquery-textext-master/tests/start_selenium_rc
new file mode 100644
index 0000000..5b0b506
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/start_selenium_rc
@@ -0,0 +1,4 @@
+#!/bin/bash
+PATH="/Applications/Firefox 5.0.1.app/Contents/MacOS":$PATH
+java -jar selenium-server-standalone-2.15.0.jar -firefoxProfileTemplate "./firefox_profile"
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/test_ajax.js b/src/main/webapp/js/jquery-textext-master/tests/test_ajax.js
new file mode 100644
index 0000000..2f5ae82
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/test_ajax.js
@@ -0,0 +1,106 @@
+var soda = require('soda'),
+ assert = require('assert'),
+ common = require('./common')
+ ;
+
+function beginAjaxTest(exampleId, test)
+{
+ return function(browser)
+ {
+ browser
+ .open('/manual/plugins/ajax.html')
+ .clickAndWait('css=#example-' + exampleId)
+ .and(common.verifyTextExt)
+ .and(test)
+ .and(common.screenshot('ajax-' + exampleId))
+ ;
+ };
+};
+
+function testLoadingMessage()
+{
+ function inject()
+ {
+ var originalAjax = $.ajax;
+
+ $.ajax = function()
+ {
+ var args = arguments,
+ self = this
+ ;
+
+ setTimeout(
+ function()
+ {
+ originalAjax.apply(self, args);
+ },
+ 1000
+ );
+ };
+ };
+
+ return function(browser)
+ {
+ browser.and(
+ beginAjaxTest('ajax-with-autocomplete', function(browser)
+ {
+ var loadingMessage = common.css.dropdown + ' .text-suggestion.text-loading';
+
+ browser
+ .runScript("(" + inject.toString() + ")();")
+
+ .and(common.focusInput)
+
+ .typeKeys(common.css.textarea, 'ba')
+ .waitForVisible(common.css.dropdown)
+
+ // since we delayed AJAX call in the test, the loading message
+ // should show up for us before the items are loaded
+ .assertVisible(loadingMessage)
+
+ // wait for the loading message to disappear
+ .waitForElementNotPresent(loadingMessage)
+
+ // verify that suggestion is present
+ .and(common.assertSuggestionItem('Basic'))
+
+ // run the autocomplete tests
+ .and(common.clearInput)
+ .and(common.testAutocompleteFunctionality())
+ ;
+ })
+ );
+ };
+};
+
+function testWithTags()
+{
+ return function(browser)
+ {
+ browser.and(
+ beginAjaxTest('ajax-with-filter-tags-and-autocomplete', function(browser)
+ {
+ browser
+ .and(common.testAjaxFunctionality())
+ .and(common.clearInput)
+ .and(common.testAutocompleteFunctionality(function() {}))
+ .and(common.testFilterFunctionality())
+ ;
+ })
+ );
+ };
+};
+
+function run(browser)
+{
+ browser
+ .and(testLoadingMessage())
+ .and(testWithTags())
+ ;
+};
+
+module.exports = run;
+
+if(require.main == module)
+ common.runModule(run);
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/test_autocomplete.js b/src/main/webapp/js/jquery-textext-master/tests/test_autocomplete.js
new file mode 100644
index 0000000..c07bc02
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/test_autocomplete.js
@@ -0,0 +1,36 @@
+var soda = require('soda'),
+ assert = require('assert'),
+ common = require('./common')
+ ;
+
+function testAutocomplete(exampleId, finalAssert)
+{
+ return function(browser)
+ {
+ browser
+ .open('/manual/plugins/autocomplete.html')
+ .clickAndWait('css=#example-' + exampleId)
+
+ .and(common.verifyTextExt)
+ .and(common.testAutocompleteFunctionality(finalAssert))
+ .and(common.screenshot('autocomplete-' + exampleId))
+ ;
+ };
+};
+
+function run(browser)
+{
+ browser
+ .and(testAutocomplete('autocomplete'))
+ .and(testAutocomplete('autocomplete-with-filter'))
+ .and(testAutocomplete('autocomplete-with-custom-render'))
+ .and(testAutocomplete('autocomplete-with-tags', common.testTagFunctionality()))
+ .and(testAutocomplete('autocomplete-with-tags-and-filter', common.testFilterFunctionality()))
+ ;
+};
+
+module.exports = run;
+
+if(require.main == module)
+ common.runModule(run);
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/test_filter.js b/src/main/webapp/js/jquery-textext-master/tests/test_filter.js
new file mode 100644
index 0000000..c0dfec8
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/test_filter.js
@@ -0,0 +1,51 @@
+var soda = require('soda'),
+ assert = require('assert'),
+ common = require('./common')
+ ;
+
+function testFilter(exampleId)
+{
+ return function(browser)
+ {
+ browser
+ .open('/manual/plugins/filter.html')
+ .clickAndWait('css=#example-' + exampleId)
+
+ .and(common.verifyTextExt)
+ .and(common.testFilterFunctionality())
+ .and(common.screenshot('filter-' + exampleId))
+ ;
+ };
+};
+
+function testTags()
+{
+ return function(browser)
+ {
+ browser
+ .and(common.typeAndValidateTag('PHP'))
+ .and(common.typeAndValidateTag('Ruby'))
+ .and(common.typeAndValidateTag('Go'))
+ ;
+ };
+};
+
+function run(browser)
+{
+ browser
+ .and(testFilter('filter-with-static-list-of-items'))
+ .and(testTags())
+
+ .and(testFilter('filter-using-suggestions'))
+ .and(testTags())
+
+ .and(testFilter('autocomplete-with-filter'))
+ .and(testFilter('filter'))
+ ;
+};
+
+module.exports = run;
+
+if(require.main == module)
+ common.runModule(run);
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/test_focus.js b/src/main/webapp/js/jquery-textext-master/tests/test_focus.js
new file mode 100644
index 0000000..56c37e9
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/test_focus.js
@@ -0,0 +1,46 @@
+var soda = require('soda'),
+ assert = require('assert'),
+ common = require('./common')
+ ;
+
+var focus = common.css.focus,
+ textarea = common.css.textarea
+ ;
+
+function testFocusFunctionality(browser)
+{
+ browser
+ .fireEvent(textarea, 'focus')
+ .waitForVisible(focus)
+ .fireEvent(textarea, 'blur')
+ .waitForNotVisible(focus)
+ ;
+};
+
+function testFocus(exampleId)
+{
+ return function(browser)
+ {
+ browser
+ .open('/manual/plugins/focus.html')
+ .clickAndWait('css=#example-' + exampleId)
+
+ .and(common.verifyTextExt)
+ .and(testFocusFunctionality)
+ .and(common.screenshot('focus-' + exampleId))
+ ;
+ };
+};
+
+function run(browser)
+{
+ browser
+ .and(testFocus('focus'))
+ ;
+};
+
+module.exports = run;
+
+if(require.main == module)
+ common.runModule(run);
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/test_prompt.js b/src/main/webapp/js/jquery-textext-master/tests/test_prompt.js
new file mode 100644
index 0000000..18a0ed5
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/test_prompt.js
@@ -0,0 +1,40 @@
+var soda = require('soda'),
+ assert = require('assert'),
+ common = require('./common')
+ ;
+
+function testPrompt(exampleId, secondary)
+{
+ return function(browser)
+ {
+ browser
+ .open('/manual/plugins/prompt.html')
+ .clickAndWait('css=#example-' + exampleId)
+
+ .and(common.verifyTextExt)
+ .and(common.testPromptFunctionality(secondary))
+ .and(common.screenshot('prompt-' + exampleId))
+ ;
+ };
+};
+
+function run(browser)
+{
+ browser
+ .and(testPrompt('prompt', common.testPlainInputFunctionality()))
+ .and(testPrompt('prompt-with-autocomplete-and-arrow', function(browser)
+ {
+ browser
+ .and(common.testArrowFunctionality())
+ .and(common.testAutocompleteFunctionality())
+ ;
+ }))
+ .and(testPrompt('prompt-with-tags', common.testTagFunctionality()))
+ ;
+};
+
+module.exports = run;
+
+if(require.main == module)
+ common.runModule(run);
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/test_tags.js b/src/main/webapp/js/jquery-textext-master/tests/test_tags.js
new file mode 100644
index 0000000..61d3dea
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/test_tags.js
@@ -0,0 +1,36 @@
+var soda = require('soda'),
+ assert = require('assert'),
+ common = require('./common')
+ ;
+
+function testTags(exampleId, wrap)
+{
+ return function(browser)
+ {
+ browser
+ .open('/manual/plugins/tags.html')
+ .clickAndWait('css=#example-' + exampleId)
+
+ .and(common.verifyTextExt)
+ .and(common.testTagFunctionality(wrap))
+ .and(common.screenshot('tags-' + exampleId))
+ ;
+ };
+};
+
+function run(browser)
+{
+ browser
+ .and(testTags('tags'))
+ .and(testTags('tags-with-items'))
+ .and(testTags('tags-with-custom-labels', { label: function(v) { return '[ ' + v + ' ]' } }))
+ .and(testTags('tags-with-custom-rendering'))
+ .and(testTags('tags-with-custom-data-objects', { object: function(v) { return { name : v } }} ))
+ ;
+};
+
+module.exports = run;
+
+if(require.main == module)
+ common.runModule(run);
+
diff --git a/src/main/webapp/js/jquery-textext-master/tests/tests.js b/src/main/webapp/js/jquery-textext-master/tests/tests.js
new file mode 100644
index 0000000..bd7eed3
--- /dev/null
+++ b/src/main/webapp/js/jquery-textext-master/tests/tests.js
@@ -0,0 +1,16 @@
+var soda = require('soda'),
+ assert = require('assert'),
+ common = require('./common')
+ ;
+
+common.runModule(function(browser)
+{
+ browser
+ .and(require('./test_autocomplete.js'))
+ .and(require('./test_tags.js'))
+ .and(require('./test_filter.js'))
+ .and(require('./test_focus.js'))
+ .and(require('./test_prompt.js'))
+ ;
+});
+
diff --git a/src/main/webapp/js/jquery_datatables/dataTables.buttons.min.js b/src/main/webapp/js/jquery_datatables/dataTables.buttons.min.js
new file mode 100644
index 0000000..f8dcc3c
--- /dev/null
+++ b/src/main/webapp/js/jquery_datatables/dataTables.buttons.min.js
@@ -0,0 +1,32 @@
+/*!
+ Buttons for DataTables 1.0.3
+ ©2015 SpryMedia Ltd - datatables.net/license
+*/
+(function(r,n,m){var p=function(e,j){var p=0,s=0,k=j.ext.buttons,l=function(a,b){!0===b&&(b={});e.isArray(b)&&(b={buttons:b});this.c=e.extend(!0,{},l.defaults,b);b.buttons&&(this.c.buttons=b.buttons);this.s={dt:new j.Api(a),buttons:[],subButtons:[],listenKeys:"",namespace:"dtb"+p++};this.dom={container:e("<"+this.c.dom.container.tag+"/>").addClass(this.c.dom.container.className)};this._constructor()};e.extend(l.prototype,{action:function(a,b){var c=this._indexToButton(a).conf;if(b===m)return c.action;
+c.action=b;return this},active:function(a,b){this._indexToButton(a).node.toggleClass(this.c.dom.button.active,b===m?!0:b);return this},add:function(a,b){if("string"===typeof a&&-1!==a.indexOf("-")){var c=a.split("-");this.c.buttons[1*c[0]].buttons.splice(1*c[1],0,b)}else this.c.buttons.splice(1*a,0,b);this.dom.container.empty();this._buildButtons(this.c.buttons);return this},container:function(){return this.dom.container},disable:function(a){this._indexToButton(a).node.addClass(this.c.dom.button.disabled);
+return this},destroy:function(){e("body").off("keyup."+this.s.namespace);var a=this.s.buttons,b=this.s.subButtons,c,d,f;c=0;for(a=a.length;c").addClass(i.className),this._buildButtons(h.buttons,h._collection,f));h.init&&h.init.call(d.button(q),d,q,h)}}}},_buildButton:function(a,b){var c=this.c.dom.button,d=this.c.dom.buttonLiner,f=
+this.c.dom.collection,g=this.s.dt,h=function(b){return"function"===typeof b?b(g,i,a):b};b&&f.button&&(c=f.button);b&&f.buttonLiner&&(d=f.buttonLiner);if(a.available&&!a.available(g,a))return!1;var i=e("<"+c.tag+"/>").addClass(c.className).attr("tabindex",this.s.dt.settings()[0].iTabIndex).attr("aria-controls",this.s.dt.table().node().id).on("click.dtb",function(b){b.preventDefault();!i.hasClass(c.disabled)&&a.action&&a.action.call(g.button(i),b,g,i,a);i.blur()}).on("keyup.dtb",function(b){b.keyCode===
+13&&!i.hasClass(c.disabled)&&a.action&&a.action.call(g.button(i),b,g,i,a)});d.tag?i.append(e("<"+d.tag+"/>").html(h(a.text)).addClass(d.className)):i.html(h(a.text));!1===a.enabled&&i.addClass(c.disabled);a.className&&i.addClass(a.className);a.namespace||(a.namespace=".dt-button-"+s++);d=(d=this.c.dom.buttonContainer)?e("<"+d.tag+"/>").addClass(d.className).append(i):i;this._addKey(a);return{node:i,inserter:d}},_indexToButton:function(a){if("number"===typeof a||-1===a.indexOf("-"))return this.s.buttons[1*
+a];a=a.split("-");return this.s.subButtons[1*a[0]][1*a[1]]},_keypress:function(a,b){var c,d,f,g;f=this.s.buttons;var h=this.s.subButtons,i=function(c,d){if(c.key)if(c.key===a)d.click();else if(e.isPlainObject(c.key)&&c.key.key===a&&(!c.key.shiftKey||b.shiftKey))if(!c.key.altKey||b.altKey)if(!c.key.ctrlKey||b.ctrlKey)(!c.key.metaKey||b.metaKey)&&d.click()};c=0;for(d=f.length;c ").addClass(b).css("display","none").appendTo("body").fadeIn(c):
+e("body > div."+b).fadeOut(c,function(){e(this).remove()})};l.instanceSelector=function(a,b){if(!a)return e.map(b,function(a){return a.inst});var c=[],d=e.map(b,function(a){return a.name}),f=function(a){if(e.isArray(a))for(var h=0,i=a.length;hb&&d._collection.css("left",a.left-(c-b))):(a=d._collection.height()/2,a>e(r).height()/2&&(a=e(r).height()/2),d._collection.css("marginTop",-1*a));d.background&&l.background(!0,d.backgroundClassName,d.fade);setTimeout(function(){e(n).on("click.dtb-collection",function(a){if(!e(a.target).parents().andSelf().filter(d._collection).length){d._collection.fadeOut(d.fade,
+function(){d._collection.detach()});l.background(false,d.backgroundClassName,d.fade);e(n).off("click.dtb-collection")}})},10)},background:!0,collectionLayout:"",backgroundClassName:"dt-button-background",fade:400},copy:function(a,b){if(b.preferHtml&&k.copyHtml5)return"copyHtml5";if(k.copyFlash&&k.copyFlash.available(a,b))return"copyFlash";if(k.copyHtml5)return"copyHtml5"},csv:function(a,b){if(k.csvHtml5&&k.csvHtml5.available(a,b))return"csvHtml5";if(k.csvFlash&&k.csvFlash.available(a,b))return"csvFlash"},
+excel:function(a,b){if(k.excelHtml5&&k.excelHtml5.available(a,b))return"excelHtml5";if(k.excelFlash&&k.excelFlash.available(a,b))return"excelFlash"},pdf:function(a,b){if(k.pdfHtml5&&k.pdfHtml5.available(a,b))return"pdfHtml5";if(k.pdfFlash&&k.pdfFlash.available(a,b))return"pdfFlash"}});j.Api.register("buttons()",function(a,b){b===m&&(b=a,a=m);return this.iterator(!0,"table",function(c){if(c._buttons)return l.buttonSelector(l.instanceSelector(a,c._buttons),b)},!0)});j.Api.register("button()",function(a,
+b){var c=this.buttons(a,b);1 ').html(a?
+""+a+" ":"").append(e("
")["string"===typeof b?"html":"append"](b)).css("display","none").appendTo("body").fadeIn();c!==m&&0!==c&&(o=setTimeout(function(){d.buttons.info(!1)},c));return this});j.Api.register("buttons.exportData()",function(a){if(this.context.length){for(var b=new j.Api(this.context[0]),c=e.extend(!0,{},{rows:null,columns:"",modifier:{search:"applied",order:"applied"},orthogonal:"display",stripHtml:!0,stripNewlines:!0,trim:!0},a),d=function(a){if("string"!==typeof a)return a;
+c.stripHtml&&(a=a.replace(/<.*?>/g,""));c.trim&&(a=a.replace(/^\s+|\s+$/g,""));c.stripNewlines&&(a=a.replace(/\n/g," "));return a},a=b.columns(c.columns).indexes().map(function(a){return d(b.column(a).header().innerHTML)}).toArray(),f=b.table().footer()?b.columns(c.columns).indexes().map(function(a){return(a=b.column(a).footer())?d(a.innerHTML):""}).toArray():null,g=b.cells(c.rows,c.columns,c.modifier).render(c.orthogonal).toArray(),h=a.length,i=g.length/h,k=Array(i),l=0,m=0;m b.width ? -1 : 0;
+ } );
+
+ // Determine which columns are already hidden, and should therefore
+ // remain hidden. todo - should this be done? See thread 22677
+ //
+ // this.s.alwaysHidden = dt.columns(':hidden').indexes();
+
+ this._classLogic();
+ this._resizeAuto();
+
+ // Details handler
+ var details = this.c.details;
+ if ( details.type ) {
+ that._detailsInit();
+ this._detailsVis();
+
+ dt.on( 'column-visibility.dtr', function () {
+ that._detailsVis();
+ } );
+
+ // Redraw the details box on each draw. This is used until
+ // DataTables implements a native `updated` event for rows
+ dt.on( 'draw.dtr', function () {
+ dt.rows( {page: 'current'} ).iterator( 'row', function ( settings, idx ) {
+ var row = dt.row( idx );
+
+ if ( row.child.isShown() ) {
+ var info = that.c.details.renderer( dt, idx );
+ row.child( info, 'child' ).show();
+ }
+ } );
+ } );
+
+ $(dt.table().node()).addClass( 'dtr-'+details.type );
+ }
+
+ // First pass - draw the table for the current viewport size
+ this._resize();
+ },
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Private methods
+ */
+
+ /**
+ * Calculate the visibility for the columns in a table for a given
+ * breakpoint. The result is pre-determined based on the class logic if
+ * class names are used to control all columns, but the width of the table
+ * is also used if there are columns which are to be automatically shown
+ * and hidden.
+ *
+ * @param {string} breakpoint Breakpoint name to use for the calculation
+ * @return {array} Array of boolean values initiating the visibility of each
+ * column.
+ * @private
+ */
+ _columnsVisiblity: function ( breakpoint )
+ {
+ var dt = this.s.dt;
+ var columns = this.s.columns;
+ var i, ien;
+
+ // Class logic - determine which columns are in this breakpoint based
+ // on the classes. If no class control (i.e. `auto`) then `-` is used
+ // to indicate this to the rest of the function
+ var display = $.map( columns, function ( col ) {
+ return col.auto && col.minWidth === null ?
+ false :
+ col.auto === true ?
+ '-' :
+ $.inArray( breakpoint, col.includeIn ) !== -1;
+ } );
+
+ // Auto column control - first pass: how much width is taken by the
+ // ones that must be included from the non-auto columns
+ var requiredWidth = 0;
+ for ( i=0, ien=display.length ; i= size ) {
+ add( colIdx, breakpoints[i].name );
+ }
+ }
+ }
+ else if ( operator === 'not-' ) {
+ // Add all but this breakpoint (xxx need extra information)
+
+ for ( i=0, ien=breakpoints.length ; i=0 ; i-- ) {
+ if ( width <= breakpoints[i].width ) {
+ breakpoint = breakpoints[i].name;
+ break;
+ }
+ }
+
+ // Show the columns for that break point
+ var columnsVis = this._columnsVisiblity( breakpoint );
+
+ // Set the class before the column visibility is changed so event
+ // listeners know what the state is. Need to determine if there are
+ // any columns that are not visible but can be shown
+ var collapsedClass = false;
+ for ( i=0, ien=columns.length ; i ')
+ .append( cells )
+ .appendTo( clonedHeader );
+
+ // In the inline case extra padding is applied to the first column to
+ // give space for the show / hide icon. We need to use this in the
+ // calculation
+ if ( this.c.details.type === 'inline' ) {
+ $(clonedTable).addClass( 'dtr-inline collapsed' );
+ }
+
+ var inserted = $('
')
+ .css( {
+ width: 1,
+ height: 1,
+ overflow: 'hidden'
+ } )
+ .append( clonedTable );
+
+ // Remove columns which are not to be included
+ inserted.find('th.never, td.never').remove();
+
+ inserted.insertBefore( dt.table().node() );
+
+ // The cloned header now contains the smallest that each column can be
+ dt.columns().eq(0).each( function ( idx ) {
+ columns[idx].minWidth = cells[ idx ].offsetWidth || 0;
+ } );
+
+ inserted.remove();
+ }
+};
+
+
+/**
+ * List of default breakpoints. Each item in the array is an object with two
+ * properties:
+ *
+ * * `name` - the breakpoint name.
+ * * `width` - the breakpoint width
+ *
+ * @name Responsive.breakpoints
+ * @static
+ */
+Responsive.breakpoints = [
+ { name: 'desktop', width: Infinity },
+ { name: 'tablet-l', width: 1024 },
+ { name: 'tablet-p', width: 768 },
+ { name: 'mobile-l', width: 480 },
+ { name: 'mobile-p', width: 320 }
+];
+
+
+/**
+ * Responsive default settings for initialisation
+ *
+ * @namespace
+ * @name Responsive.defaults
+ * @static
+ */
+Responsive.defaults = {
+ /**
+ * List of breakpoints for the instance. Note that this means that each
+ * instance can have its own breakpoints. Additionally, the breakpoints
+ * cannot be changed once an instance has been creased.
+ *
+ * @type {Array}
+ * @default Takes the value of `Responsive.breakpoints`
+ */
+ breakpoints: Responsive.breakpoints,
+
+ /**
+ * Enable / disable auto hiding calculations. It can help to increase
+ * performance slightly if you disable this option, but all columns would
+ * need to have breakpoint classes assigned to them
+ *
+ * @type {Boolean}
+ * @default `true`
+ */
+ auto: true,
+
+ /**
+ * Details control. If given as a string value, the `type` property of the
+ * default object is set to that value, and the defaults used for the rest
+ * of the object - this is for ease of implementation.
+ *
+ * The object consists of the following properties:
+ *
+ * * `renderer` - function that is called for display of the child row data.
+ * The default function will show the data from the hidden columns
+ * * `target` - Used as the selector for what objects to attach the child
+ * open / close to
+ * * `type` - `false` to disable the details display, `inline` or `column`
+ * for the two control types
+ *
+ * @type {Object|string}
+ */
+ details: {
+ renderer: function ( api, rowIdx ) {
+ var data = api.cells( rowIdx, ':hidden' ).eq(0).map( function ( cell ) {
+ var header = $( api.column( cell.column ).header() );
+ var idx = api.cell( cell ).index();
+
+ if ( header.hasClass( 'control' ) || header.hasClass( 'never' ) ) {
+ return '';
+ }
+
+ // Use a non-public DT API method to render the data for display
+ // This needs to be updated when DT adds a suitable method for
+ // this type of data retrieval
+ var dtPrivate = api.settings()[0];
+ var cellData = dtPrivate.oApi._fnGetCellData(
+ dtPrivate, idx.row, idx.column, 'display'
+ );
+ var title = header.text();
+ if ( title ) {
+ title = title + ':';
+ }
+
+ return ''+
+ ''+
+ title+
+ ' '+
+ ''+
+ cellData+
+ ' '+
+ ' ';
+ } ).toArray().join('');
+
+ return data ?
+ $('').append( data ) :
+ false;
+ },
+
+ target: 0,
+
+ type: 'inline'
+ }
+};
+
+
+/*
+ * API
+ */
+var Api = $.fn.dataTable.Api;
+
+// Doesn't do anything - work around for a bug in DT... Not documented
+Api.register( 'responsive()', function () {
+ return this;
+} );
+
+Api.register( 'responsive.index()', function ( li ) {
+ li = $(li);
+
+ return {
+ column: li.data('dtr-index'),
+ row: li.parent().data('dtr-index')
+ };
+} );
+
+Api.register( 'responsive.rebuild()', function () {
+ return this.iterator( 'table', function ( ctx ) {
+ if ( ctx._responsive ) {
+ ctx._responsive._classLogic();
+ }
+ } );
+} );
+
+Api.register( 'responsive.recalc()', function () {
+ return this.iterator( 'table', function ( ctx ) {
+ if ( ctx._responsive ) {
+ ctx._responsive._resizeAuto();
+ ctx._responsive._resize();
+ }
+ } );
+} );
+
+
+/**
+ * Version information
+ *
+ * @name Responsive.version
+ * @static
+ */
+Responsive.version = '1.0.7';
+
+
+$.fn.dataTable.Responsive = Responsive;
+$.fn.DataTable.Responsive = Responsive;
+
+// Attach a listener to the document which listens for DataTables initialisation
+// events so we can automatically initialise
+$(document).on( 'init.dt.dtr', function (e, settings, json) {
+ if ( e.namespace !== 'dt' ) {
+ return;
+ }
+
+ if ( $(settings.nTable).hasClass( 'responsive' ) ||
+ $(settings.nTable).hasClass( 'dt-responsive' ) ||
+ settings.oInit.responsive ||
+ DataTable.defaults.responsive
+ ) {
+ var init = settings.oInit.responsive;
+
+ if ( init !== false ) {
+ new Responsive( settings, $.isPlainObject( init ) ? init : {} );
+ }
+ }
+} );
+
+return Responsive;
+}; // /factory
+
+
+// Define as an AMD module if possible
+if ( typeof define === 'function' && define.amd ) {
+ define( ['jquery', 'datatables'], factory );
+}
+else if ( typeof exports === 'object' ) {
+ // Node/CommonJS
+ factory( require('jquery'), require('datatables') );
+}
+else if ( jQuery && !jQuery.fn.dataTable.Responsive ) {
+ // Otherwise simply initialise as normal, stopping multiple evaluation
+ factory( jQuery, jQuery.fn.dataTable );
+}
+
+
+})(window, document);
diff --git a/src/main/webapp/js/jquery_datatables/dataTables.responsive.min.js b/src/main/webapp/js/jquery_datatables/dataTables.responsive.min.js
new file mode 100644
index 0000000..444b748
--- /dev/null
+++ b/src/main/webapp/js/jquery_datatables/dataTables.responsive.min.js
@@ -0,0 +1,25 @@
+/*!
+ Responsive 2.0.2
+ 2014-2016 SpryMedia Ltd - datatables.net/license
+*/
+(function(c){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(j){return c(j,window,document)}):"object"===typeof exports?module.exports=function(j,k){j||(j=window);if(!k||!k.fn.dataTable)k=require("datatables.net")(j,k).$;return c(k,j,j.document)}:c(jQuery,window,document)})(function(c,j,k,p){var n=c.fn.dataTable,l=function(a,b){if(!n.versionCheck||!n.versionCheck("1.10.3"))throw"DataTables Responsive requires DataTables 1.10.3 or newer";this.s={dt:new n.Api(a),columns:[],
+current:[]};this.s.dt.settings()[0].responsive||(b&&"string"===typeof b.details?b.details={type:b.details}:b&&!1===b.details?b.details={type:!1}:b&&!0===b.details&&(b.details={type:"inline"}),this.c=c.extend(!0,{},l.defaults,n.defaults.responsive,b),a.responsive=this,this._constructor())};c.extend(l.prototype,{_constructor:function(){var a=this,b=this.s.dt,d=b.settings()[0],e=c(j).width();b.settings()[0]._responsive=this;c(j).on("resize.dtr orientationchange.dtr",n.util.throttle(function(){var b=
+c(j).width();b!==e&&(a._resize(),e=b)}));d.oApi._fnCallbackReg(d,"aoRowCreatedCallback",function(e){-1!==c.inArray(!1,a.s.current)&&c("td, th",e).each(function(e){e=b.column.index("toData",e);!1===a.s.current[e]&&c(this).css("display","none")})});b.on("destroy.dtr",function(){b.off(".dtr");c(b.table().body()).off(".dtr");c(j).off("resize.dtr orientationchange.dtr");c.each(a.s.current,function(b,e){!1===e&&a._setColumnVis(b,!0)})});this.c.breakpoints.sort(function(a,b){return a.width
+b.width?-1:0});this._classLogic();this._resizeAuto();d=this.c.details;!1!==d.type&&(a._detailsInit(),b.on("column-visibility.dtr",function(){a._classLogic();a._resizeAuto();a._resize()}),b.on("draw.dtr",function(){a._redrawChildren()}),c(b.table().node()).addClass("dtr-"+d.type));b.on("column-reorder.dtr",function(){a._classLogic();a._resizeAuto();a._resize()});b.on("column-sizing.dtr",function(){a._resize()});b.on("init.dtr",function(){a._resizeAuto();a._resize();c.inArray(false,a.s.current)&&b.columns.adjust()});
+this._resize()},_columnsVisiblity:function(a){var b=this.s.dt,d=this.s.columns,e,f,g=d.map(function(a,b){return{columnIdx:b,priority:a.priority}}).sort(function(a,b){return a.priority!==b.priority?a.priority-b.priority:a.columnIdx-b.columnIdx}),h=c.map(d,function(b){return b.auto&&null===b.minWidth?!1:!0===b.auto?"-":-1!==c.inArray(a,b.includeIn)}),m=0;e=0;for(f=h.length;eb-d[i].minWidth?(m=!0,h[i]=!1):h[i]=!0,b-=d[i].minWidth)}g=!1;e=0;for(f=d.length;e=
+g&&f(c,b[d].name)}else{if("not-"===i){d=0;for(i=b.length;d ").append(h).appendTo(f)}c(" ").append(g).appendTo(e);"inline"===this.c.details.type&&c(d).addClass("dtr-inline collapsed");d=c("
").css({width:1,height:1,overflow:"hidden"}).append(d);d.insertBefore(a.table().node());
+g.each(function(c){c=a.column.index("fromVisible",c);b[c].minWidth=this.offsetWidth||0});d.remove()}},_setColumnVis:function(a,b){var d=this.s.dt,e=b?"":"none";c(d.column(a).header()).css("display",e);c(d.column(a).footer()).css("display",e);d.column(a).nodes().to$().css("display",e)},_tabIndexes:function(){var a=this.s.dt,b=a.cells({page:"current"}).nodes().to$(),d=a.settings()[0],e=this.c.details.target;b.filter("[data-dtr-keyboard]").removeData("[data-dtr-keyboard]");c("number"===typeof e?":eq("+
+e+")":e,a.rows({page:"current"}).nodes()).attr("tabIndex",d.iTabIndex).data("dtr-keyboard",1)}});l.breakpoints=[{name:"desktop",width:Infinity},{name:"tablet-l",width:1024},{name:"tablet-p",width:768},{name:"mobile-l",width:480},{name:"mobile-p",width:320}];l.display={childRow:function(a,b,d){if(b){if(c(a.node()).hasClass("parent"))return a.child(d(),"child").show(),!0}else{if(a.child.isShown())return a.child(!1),c(a.node()).removeClass("parent"),!1;a.child(d(),"child").show();c(a.node()).addClass("parent");
+return!0}},childRowImmediate:function(a,b,d){if(!b&&a.child.isShown()||!a.responsive.hasHidden())return a.child(!1),c(a.node()).removeClass("parent"),!1;a.child(d(),"child").show();c(a.node()).addClass("parent");return!0},modal:function(a){return function(b,d,e){if(d)c("div.dtr-modal-content").empty().append(e());else{var f=function(){g.remove();c(k).off("keypress.dtr")},g=c('
').append(c('
').append(c('
').append(e())).append(c('×
').click(function(){f()}))).append(c('
').click(function(){f()})).appendTo("body");
+c(k).on("keyup.dtr",function(a){27===a.keyCode&&(a.stopPropagation(),f())})}a&&a.header&&c("div.dtr-modal-content").prepend(""+a.header(b)+" ")}}};l.defaults={breakpoints:l.breakpoints,auto:!0,details:{display:l.display.childRow,renderer:function(a,b,d){return(a=c.map(d,function(a){return a.hidden?''+a.title+' '+a.data+" ":
+""}).join(""))?c('').append(a):!1},target:0,type:"inline"},orthogonal:"display"};var o=c.fn.dataTable.Api;o.register("responsive()",function(){return this});o.register("responsive.index()",function(a){a=c(a);return{column:a.data("dtr-index"),row:a.parent().data("dtr-index")}});o.register("responsive.rebuild()",function(){return this.iterator("table",function(a){a._responsive&&a._responsive._classLogic()})});o.register("responsive.recalc()",function(){return this.iterator("table",
+function(a){a._responsive&&(a._responsive._resizeAuto(),a._responsive._resize())})});o.register("responsive.hasHidden()",function(){var a=this.context[0];return a._responsive?-1!==c.inArray(!1,a._responsive.s.current):!1});l.version="2.0.2";c.fn.dataTable.Responsive=l;c.fn.DataTable.Responsive=l;c(k).on("preInit.dt.dtr",function(a,b){if("dt"===a.namespace&&(c(b.nTable).hasClass("responsive")||c(b.nTable).hasClass("dt-responsive")||b.oInit.responsive||n.defaults.responsive)){var d=b.oInit.responsive;
+!1!==d&&new l(b,c.isPlainObject(d)?d:{})}});return l});
\ No newline at end of file
diff --git a/src/main/webapp/js/jquery_datatables/dataTables.responsive.min2.js b/src/main/webapp/js/jquery_datatables/dataTables.responsive.min2.js
new file mode 100644
index 0000000..ae3e267
--- /dev/null
+++ b/src/main/webapp/js/jquery_datatables/dataTables.responsive.min2.js
@@ -0,0 +1,19 @@
+/*!
+ Responsive 1.0.7
+ 2014-2015 SpryMedia Ltd - datatables.net/license
+*/
+(function(n,p){var o=function(e,k){var h=function(d,a){if(!k.versionCheck||!k.versionCheck("1.10.1"))throw"DataTables Responsive requires DataTables 1.10.1 or newer";this.s={dt:new k.Api(d),columns:[]};this.s.dt.settings()[0].responsive||(a&&"string"===typeof a.details&&(a.details={type:a.details}),this.c=e.extend(!0,{},h.defaults,k.defaults.responsive,a),d.responsive=this,this._constructor())};h.prototype={_constructor:function(){var d=this,a=this.s.dt;a.settings()[0]._responsive=this;e(n).on("resize.dtr orientationchange.dtr",
+a.settings()[0].oApi._fnThrottle(function(){d._resize()}));a.on("destroy.dtr",function(){e(n).off("resize.dtr orientationchange.dtr draw.dtr")});this.c.breakpoints.sort(function(a,c){return a.widthc.width?-1:0});this._classLogic();this._resizeAuto();var c=this.c.details;c.type&&(d._detailsInit(),this._detailsVis(),a.on("column-visibility.dtr",function(){d._detailsVis()}),a.on("draw.dtr",function(){a.rows({page:"current"}).iterator("row",function(b,c){var f=a.row(c);if(f.child.isShown()){var i=
+d.c.details.renderer(a,c);f.child(i,"child").show()}})}),e(a.table().node()).addClass("dtr-"+c.type));this._resize()},_columnsVisiblity:function(d){var a=this.s.dt,c=this.s.columns,b,g,f=e.map(c,function(a){return a.auto&&null===a.minWidth?!1:!0===a.auto?"-":-1!==e.inArray(d,a.includeIn)}),i=0;b=0;for(g=f.length;ba-c[b].minWidth?(i=!0,f[b]=!1):f[b]=!0,a-=c[b].minWidth);a=!1;b=0;for(g=c.length;b=j&&b(f,a[g].name)}else{if("not-"===e){g=0;for(e=a.length;g ").append(f).appendTo(b);"inline"===this.c.details.type&&e(c).addClass("dtr-inline collapsed");c=e("
").css({width:1,height:1,overflow:"hidden"}).append(c);c.find("th.never, td.never").remove();c.insertBefore(d.table().node());d.columns().eq(0).each(function(b){a[b].minWidth=f[b].offsetWidth||0});c.remove()}}};h.breakpoints=[{name:"desktop",width:Infinity},{name:"tablet-l",width:1024},{name:"tablet-p",width:768},{name:"mobile-l",width:480},{name:"mobile-p",width:320}];h.defaults={breakpoints:h.breakpoints,
+auto:!0,details:{renderer:function(d,a){var c=d.cells(a,":hidden").eq(0).map(function(a){var c=e(d.column(a.column).header()),a=d.cell(a).index();if(c.hasClass("control")||c.hasClass("never"))return"";var f=d.settings()[0],f=f.oApi._fnGetCellData(f,a.row,a.column,"display");(c=c.text())&&(c+=":");return''+c+' '+f+" "}).toArray().join("");return c?e('').append(c):!1},target:0,
+type:"inline"}};var m=e.fn.dataTable.Api;m.register("responsive()",function(){return this});m.register("responsive.index()",function(d){d=e(d);return{column:d.data("dtr-index"),row:d.parent().data("dtr-index")}});m.register("responsive.rebuild()",function(){return this.iterator("table",function(d){d._responsive&&d._responsive._classLogic()})});m.register("responsive.recalc()",function(){return this.iterator("table",function(d){d._responsive&&(d._responsive._resizeAuto(),d._responsive._resize())})});
+h.version="1.0.7";e.fn.dataTable.Responsive=h;e.fn.DataTable.Responsive=h;e(p).on("init.dt.dtr",function(d,a){if("dt"===d.namespace&&(e(a.nTable).hasClass("responsive")||e(a.nTable).hasClass("dt-responsive")||a.oInit.responsive||k.defaults.responsive)){var c=a.oInit.responsive;!1!==c&&new h(a,e.isPlainObject(c)?c:{})}});return h};"function"===typeof define&&define.amd?define(["jquery","datatables"],o):"object"===typeof exports?o(require("jquery"),require("datatables")):jQuery&&!jQuery.fn.dataTable.Responsive&&
+o(jQuery,jQuery.fn.dataTable)})(window,document);
diff --git a/src/main/webapp/js/jquery_datatables/dataTables.select.min.js b/src/main/webapp/js/jquery_datatables/dataTables.select.min.js
new file mode 100644
index 0000000..a9231ff
--- /dev/null
+++ b/src/main/webapp/js/jquery_datatables/dataTables.select.min.js
@@ -0,0 +1,23 @@
+/*!
+ Select for DataTables 1.0.1
+ 2015 SpryMedia Ltd - datatables.net/license/mit
+*/
+(function(j,u,i){j=function(e,h){function j(a){var c=a.settings()[0]._select.selector;e(a.table().body()).off("mousedown.dtSelect",c).off("mouseup.dtSelect",c).off("click.dtSelect",c);e("body").off("click.dtSelect")}function s(a){var c=e(a.table().body()),b=a.settings()[0],d=b._select.selector;c.on("mousedown.dtSelect",d,function(b){if(b.shiftKey)c.css("-moz-user-select","none").one("selectstart.dtSelect",d,function(){return!1})}).on("mouseup.dtSelect",d,function(){c.css("-moz-user-select","")}).on("click.dtSelect",
+d,function(b){var d=a.select.items(),f=a.cell(this).index(),k=a.settings()[0];e(b.target).closest("tbody")[0]==c[0]&&a.cell(b.target).any()&&("row"===d?(d=f.row,r(b,a,k,"row",d)):"column"===d?(d=a.cell(b.target).index().column,r(b,a,k,"column",d)):"cell"===d&&(d=a.cell(b.target).index(),r(b,a,k,"cell",d)),k._select_lastCell=f)});e("body").on("click.dtSelect",function(c){b._select.blurable&&!e(c.target).parents().filter(a.table().container()).length&&(e(c.target).parents("div.DTE").length||o(b,!0))})}
+function l(a,c,b,d){if(!d||a.flatten().length)b.unshift(a),e(a.table().node()).triggerHandler(c+".dt",b)}function t(a){var c=a.settings()[0];if(c._select.info&&c.aanFeatures.i){var b=e(' '),d=function(c,d){b.append(e(' ').append(a.i18n("select."+c+"s",{_:"%d "+c+"s selected","0":"",1:"1 "+c+" selected"},d)))};d("row",a.rows({selected:!0}).flatten().length);d("column",a.columns({selected:!0}).flatten().length);d("cell",a.cells({selected:!0}).flatten().length);
+e.each(c.aanFeatures.i,function(c,a){var a=e(a),d=a.children("span.select-info");d.length&&d.remove();""!==b.text()&&a.append(b)})}}function o(a,c){if(c||"single"===a._select.style){var b=new h.Api(a);b.rows({selected:!0}).deselect();b.columns({selected:!0}).deselect();b.cells({selected:!0}).deselect()}}function r(a,c,b,d,g){var q=c.select.style(),f=c[d](g,{selected:!0}).any();"os"===q?a.ctrlKey||a.metaKey?c[d](g).select(!f):a.shiftKey?"cell"===d?(d=b._select_lastCell||null,f=function(b,a){if(b>a)var d=
+a,a=b,b=d;var f=!1;return c.columns(":visible").indexes().filter(function(c){c===b&&(f=!0);return c===a?(f=!1,!0):f})},a=function(b,a){var d=c.rows({search:"applied"}).indexes();if(d.indexOf(b)>d.indexOf(a))var f=a,a=b,b=f;var g=!1;return d.filter(function(c){c===b&&(g=!0);return c===a?(g=!1,!0):g})},!c.cells({selected:!0}).any()&&!d?(f=f(0,g.column),d=a(0,g.row)):(f=f(d.column,g.column),d=a(d.row,g.row)),d=c.cells(d,f).flatten(),c.cells(g,{selected:!0}).any()?c.cells(d).deselect():c.cells(d).select()):
+(a=b._select_lastCell?b._select_lastCell[d]:null,f=c[d+"s"]({search:"applied"}).indexes(),a=e.inArray(a,f),b=e.inArray(g,f),!c[d+"s"]({selected:!0}).any()&&-1===a?f.splice(e.inArray(g,f)+1,f.length):(a>b&&(q=b,b=a,a=q),f.splice(b+1,f.length),f.splice(0,a)),c[d](g,{selected:!0}).any())?(f.splice(e.inArray(g,f),1),c[d+"s"](f).deselect()):c[d+"s"](f).select():(a=c[d+"s"]({selected:!0}),f&&1===a.flatten().length?c[d](g).deselect():(a.deselect(),c[d](g).select())):c[d](g).select(!f)}function p(a,c){return function(b){return b.i18n("buttons."+
+a,c)}}h.select={};h.select.version="1.0.1";e.each([{type:"row",prop:"aoData"},{type:"column",prop:"aoColumns"}],function(a,c){h.ext.selector[c.type].push(function(b,a,g){var a=a.selected,e,f=[];if(a===i)return g;for(var k=0,h=g.length;k */function( window, document, undefined ) {
+
+(function( factory ) {
+ "use strict";
+
+ if ( typeof define === 'function' && define.amd ) {
+ // Define as an AMD module if possible
+ define( 'datatables', ['jquery'], factory );
+ }
+ else if ( typeof exports === 'object' ) {
+ // Node/CommonJS
+ module.exports = factory( require( 'jquery' ) );
+ }
+ else if ( jQuery && !jQuery.fn.dataTable ) {
+ // Define using browser globals otherwise
+ // Prevent multiple instantiations if the script is loaded twice
+ factory( jQuery );
+ }
+}
+(/** @lends */function( $ ) {
+ "use strict";
+
+ /**
+ * DataTables is a plug-in for the jQuery Javascript library. It is a highly
+ * flexible tool, based upon the foundations of progressive enhancement,
+ * which will add advanced interaction controls to any HTML table. For a
+ * full list of features please refer to
+ * [DataTables.net](href="http://datatables.net).
+ *
+ * Note that the `DataTable` object is not a global variable but is aliased
+ * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may
+ * be accessed.
+ *
+ * @class
+ * @param {object} [init={}] Configuration object for DataTables. Options
+ * are defined by {@link DataTable.defaults}
+ * @requires jQuery 1.7+
+ *
+ * @example
+ * // Basic initialisation
+ * $(document).ready( function {
+ * $('#example').dataTable();
+ * } );
+ *
+ * @example
+ * // Initialisation with configuration options - in this case, disable
+ * // pagination and sorting.
+ * $(document).ready( function {
+ * $('#example').dataTable( {
+ * "paginate": false,
+ * "sort": false
+ * } );
+ * } );
+ */
+ var DataTable;
+
+
+ /*
+ * It is useful to have variables which are scoped locally so only the
+ * DataTables functions can access them and they don't leak into global space.
+ * At the same time these functions are often useful over multiple files in the
+ * core and API, so we list, or at least document, all variables which are used
+ * by DataTables as private variables here. This also ensures that there is no
+ * clashing of variable names and that they can easily referenced for reuse.
+ */
+
+
+ // Defined else where
+ // _selector_run
+ // _selector_opts
+ // _selector_first
+ // _selector_row_indexes
+
+ var _ext; // DataTable.ext
+ var _Api; // DataTable.Api
+ var _api_register; // DataTable.Api.register
+ var _api_registerPlural; // DataTable.Api.registerPlural
+
+ var _re_dic = {};
+ var _re_new_lines = /[\r\n]/g;
+ var _re_html = /<.*?>/g;
+ var _re_date_start = /^[\w\+\-]/;
+ var _re_date_end = /[\w\+\-]$/;
+
+ // Escape regular expression special characters
+ var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' );
+
+ // http://en.wikipedia.org/wiki/Foreign_exchange_market
+ // - \u20BD - Russian ruble.
+ // - \u20a9 - South Korean Won
+ // - \u20BA - Turkish Lira
+ // - \u20B9 - Indian Rupee
+ // - R - Brazil (R$) and South Africa
+ // - fr - Swiss Franc
+ // - kr - Swedish krona, Norwegian krone and Danish krone
+ // - \u2009 is thin space and \u202F is narrow no-break space, both used in many
+ // standards as thousands separators.
+ var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi;
+
+
+ var _empty = function ( d ) {
+ return !d || d === true || d === '-' ? true : false;
+ };
+
+
+ var _intVal = function ( s ) {
+ var integer = parseInt( s, 10 );
+ return !isNaN(integer) && isFinite(s) ? integer : null;
+ };
+
+ // Convert from a formatted number with characters other than `.` as the
+ // decimal place, to a Javascript number
+ var _numToDecimal = function ( num, decimalPoint ) {
+ // Cache created regular expressions for speed as this function is called often
+ if ( ! _re_dic[ decimalPoint ] ) {
+ _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' );
+ }
+ return typeof num === 'string' && decimalPoint !== '.' ?
+ num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) :
+ num;
+ };
+
+
+ var _isNumber = function ( d, decimalPoint, formatted ) {
+ var strType = typeof d === 'string';
+
+ // If empty return immediately so there must be a number if it is a
+ // formatted string (this stops the string "k", or "kr", etc being detected
+ // as a formatted number for currency
+ if ( _empty( d ) ) {
+ return true;
+ }
+
+ if ( decimalPoint && strType ) {
+ d = _numToDecimal( d, decimalPoint );
+ }
+
+ if ( formatted && strType ) {
+ d = d.replace( _re_formatted_numeric, '' );
+ }
+
+ return !isNaN( parseFloat(d) ) && isFinite( d );
+ };
+
+
+ // A string without HTML in it can be considered to be HTML still
+ var _isHtml = function ( d ) {
+ return _empty( d ) || typeof d === 'string';
+ };
+
+
+ var _htmlNumeric = function ( d, decimalPoint, formatted ) {
+ if ( _empty( d ) ) {
+ return true;
+ }
+
+ var html = _isHtml( d );
+ return ! html ?
+ null :
+ _isNumber( _stripHtml( d ), decimalPoint, formatted ) ?
+ true :
+ null;
+ };
+
+
+ var _pluck = function ( a, prop, prop2 ) {
+ var out = [];
+ var i=0, ien=a.length;
+
+ // Could have the test in the loop for slightly smaller code, but speed
+ // is essential here
+ if ( prop2 !== undefined ) {
+ for ( ; i')
+ .css( {
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ height: 1,
+ width: 1,
+ overflow: 'hidden'
+ } )
+ .append(
+ $('
')
+ .css( {
+ position: 'absolute',
+ top: 1,
+ left: 1,
+ width: 100,
+ overflow: 'scroll'
+ } )
+ .append(
+ $('
')
+ .css( {
+ width: '100%',
+ height: 10
+ } )
+ )
+ )
+ .appendTo( 'body' );
+
+ var outer = n.children();
+ var inner = outer.children();
+
+ // Numbers below, in order, are:
+ // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth
+ //
+ // IE6 XP: 100 100 100 83
+ // IE7 Vista: 100 100 100 83
+ // IE 8+ Windows: 83 83 100 83
+ // Evergreen Windows: 83 83 100 83
+ // Evergreen Mac with scrollbars: 85 85 100 85
+ // Evergreen Mac without scrollbars: 100 100 100 100
+
+ // Get scrollbar width
+ browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth;
+
+ // IE6/7 will oversize a width 100% element inside a scrolling element, to
+ // include the width of the scrollbar, while other browsers ensure the inner
+ // element is contained without forcing scrolling
+ //console.log( inner.offsetWidth );
+ browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100;
+
+ // In rtl text layout, some browsers (most, but not all) will place the
+ // scrollbar on the left, rather than the right.
+ browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1;
+
+ // IE8- don't provide height and width for getBoundingClientRect
+ browser.bBounding = n[0].getBoundingClientRect().width ? true : false;
+
+ n.remove();
+ }
+
+ $.extend( settings.oBrowser, DataTable.__browser );
+ settings.oScroll.iBarWidth = DataTable.__browser.barWidth;
+ }
+
+
+ /**
+ * Array.prototype reduce[Right] method, used for browsers which don't support
+ * JS 1.6. Done this way to reduce code size, since we iterate either way
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnReduce ( that, fn, init, start, end, inc )
+ {
+ var
+ i = start,
+ value,
+ isSet = false;
+
+ if ( init !== undefined ) {
+ value = init;
+ isSet = true;
+ }
+
+ while ( i !== end ) {
+ if ( ! that.hasOwnProperty(i) ) {
+ continue;
+ }
+
+ value = isSet ?
+ fn( value, that[i], i, that ) :
+ that[i];
+
+ isSet = true;
+ i += inc;
+ }
+
+ return value;
+ }
+
+ /**
+ * Add a column to the list used for the table with default values
+ * @param {object} oSettings dataTables settings object
+ * @param {node} nTh The th element for this column
+ * @memberof DataTable#oApi
+ */
+ function _fnAddColumn( oSettings, nTh )
+ {
+ // Add column to aoColumns array
+ var oDefaults = DataTable.defaults.column;
+ var iCol = oSettings.aoColumns.length;
+ var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, {
+ "nTh": nTh ? nTh : document.createElement('th'),
+ "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '',
+ "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol],
+ "mData": oDefaults.mData ? oDefaults.mData : iCol,
+ idx: iCol
+ } );
+ oSettings.aoColumns.push( oCol );
+
+ // Add search object for column specific search. Note that the `searchCols[ iCol ]`
+ // passed into extend can be undefined. This allows the user to give a default
+ // with only some of the parameters defined, and also not give a default
+ var searchCols = oSettings.aoPreSearchCols;
+ searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] );
+
+ // Use the default column options function to initialise classes etc
+ _fnColumnOptions( oSettings, iCol, $(nTh).data() );
+ }
+
+
+ /**
+ * Apply options for a column
+ * @param {object} oSettings dataTables settings object
+ * @param {int} iCol column index to consider
+ * @param {object} oOptions object with sType, bVisible and bSearchable etc
+ * @memberof DataTable#oApi
+ */
+ function _fnColumnOptions( oSettings, iCol, oOptions )
+ {
+ var oCol = oSettings.aoColumns[ iCol ];
+ var oClasses = oSettings.oClasses;
+ var th = $(oCol.nTh);
+
+ // Try to get width information from the DOM. We can't get it from CSS
+ // as we'd need to parse the CSS stylesheet. `width` option can override
+ if ( ! oCol.sWidthOrig ) {
+ // Width attribute
+ oCol.sWidthOrig = th.attr('width') || null;
+
+ // Style attribute
+ var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/);
+ if ( t ) {
+ oCol.sWidthOrig = t[1];
+ }
+ }
+
+ /* User specified column options */
+ if ( oOptions !== undefined && oOptions !== null )
+ {
+ // Backwards compatibility
+ _fnCompatCols( oOptions );
+
+ // Map camel case parameters to their Hungarian counterparts
+ _fnCamelToHungarian( DataTable.defaults.column, oOptions );
+
+ /* Backwards compatibility for mDataProp */
+ if ( oOptions.mDataProp !== undefined && !oOptions.mData )
+ {
+ oOptions.mData = oOptions.mDataProp;
+ }
+
+ if ( oOptions.sType )
+ {
+ oCol._sManualType = oOptions.sType;
+ }
+
+ // `class` is a reserved word in Javascript, so we need to provide
+ // the ability to use a valid name for the camel case input
+ if ( oOptions.className && ! oOptions.sClass )
+ {
+ oOptions.sClass = oOptions.className;
+ }
+
+ $.extend( oCol, oOptions );
+ _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
+
+ /* iDataSort to be applied (backwards compatibility), but aDataSort will take
+ * priority if defined
+ */
+ if ( oOptions.iDataSort !== undefined )
+ {
+ oCol.aDataSort = [ oOptions.iDataSort ];
+ }
+ _fnMap( oCol, oOptions, "aDataSort" );
+ }
+
+ /* Cache the data get and set functions for speed */
+ var mDataSrc = oCol.mData;
+ var mData = _fnGetObjectDataFn( mDataSrc );
+ var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null;
+
+ var attrTest = function( src ) {
+ return typeof src === 'string' && src.indexOf('@') !== -1;
+ };
+ oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && (
+ attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter)
+ );
+
+ oCol.fnGetData = function (rowData, type, meta) {
+ var innerData = mData( rowData, type, undefined, meta );
+
+ return mRender && type ?
+ mRender( innerData, type, rowData, meta ) :
+ innerData;
+ };
+ oCol.fnSetData = function ( rowData, val, meta ) {
+ return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta );
+ };
+
+ // Indicate if DataTables should read DOM data as an object or array
+ // Used in _fnGetRowElements
+ if ( typeof mDataSrc !== 'number' ) {
+ oSettings._rowReadObject = true;
+ }
+
+ /* Feature sorting overrides column specific when off */
+ if ( !oSettings.oFeatures.bSort )
+ {
+ oCol.bSortable = false;
+ th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called
+ }
+
+ /* Check that the class assignment is correct for sorting */
+ var bAsc = $.inArray('asc', oCol.asSorting) !== -1;
+ var bDesc = $.inArray('desc', oCol.asSorting) !== -1;
+ if ( !oCol.bSortable || (!bAsc && !bDesc) )
+ {
+ oCol.sSortingClass = oClasses.sSortableNone;
+ oCol.sSortingClassJUI = "";
+ }
+ else if ( bAsc && !bDesc )
+ {
+ oCol.sSortingClass = oClasses.sSortableAsc;
+ oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed;
+ }
+ else if ( !bAsc && bDesc )
+ {
+ oCol.sSortingClass = oClasses.sSortableDesc;
+ oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed;
+ }
+ else
+ {
+ oCol.sSortingClass = oClasses.sSortable;
+ oCol.sSortingClassJUI = oClasses.sSortJUI;
+ }
+ }
+
+
+ /**
+ * Adjust the table column widths for new data. Note: you would probably want to
+ * do a redraw after calling this function!
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnAdjustColumnSizing ( settings )
+ {
+ /* Not interested in doing column width calculation if auto-width is disabled */
+ if ( settings.oFeatures.bAutoWidth !== false )
+ {
+ var columns = settings.aoColumns;
+
+ _fnCalculateColumnWidths( settings );
+ for ( var i=0 , iLen=columns.length ; i=0 ; i-- )
+ {
+ def = aoColDefs[i];
+
+ /* Each definition can target multiple columns, as it is an array */
+ var aTargets = def.targets !== undefined ?
+ def.targets :
+ def.aTargets;
+
+ if ( ! $.isArray( aTargets ) )
+ {
+ aTargets = [ aTargets ];
+ }
+
+ for ( j=0, jLen=aTargets.length ; j= 0 )
+ {
+ /* Add columns that we don't yet know about */
+ while( columns.length <= aTargets[j] )
+ {
+ _fnAddColumn( oSettings );
+ }
+
+ /* Integer, basic index */
+ fn( aTargets[j], def );
+ }
+ else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 )
+ {
+ /* Negative integer, right to left column counting */
+ fn( columns.length+aTargets[j], def );
+ }
+ else if ( typeof aTargets[j] === 'string' )
+ {
+ /* Class name matching on TH element */
+ for ( k=0, kLen=columns.length ; k=0 if successful (index of new aoData entry), -1 if failed
+ * @memberof DataTable#oApi
+ */
+ function _fnAddData ( oSettings, aDataIn, nTr, anTds )
+ {
+ /* Create the object for storing information about this new row */
+ var iRow = oSettings.aoData.length;
+ var oData = $.extend( true, {}, DataTable.models.oRow, {
+ src: nTr ? 'dom' : 'data',
+ idx: iRow
+ } );
+
+ oData._aData = aDataIn;
+ oSettings.aoData.push( oData );
+
+ /* Create the cells */
+ var nTd, sThisType;
+ var columns = oSettings.aoColumns;
+
+ // Invalidate the column types as the new data needs to be revalidated
+ for ( var i=0, iLen=columns.length ; i iTarget )
+ {
+ a[i]--;
+ }
+ }
+
+ if ( iTargetIndex != -1 && splice === undefined )
+ {
+ a.splice( iTargetIndex, 1 );
+ }
+ }
+
+
+ /**
+ * Mark cached data as invalid such that a re-read of the data will occur when
+ * the cached data is next requested. Also update from the data source object.
+ *
+ * @param {object} settings DataTables settings object
+ * @param {int} rowIdx Row index to invalidate
+ * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom'
+ * or 'data'
+ * @param {int} [colIdx] Column index to invalidate. If undefined the whole
+ * row will be invalidated
+ * @memberof DataTable#oApi
+ *
+ * @todo For the modularisation of v1.11 this will need to become a callback, so
+ * the sort and filter methods can subscribe to it. That will required
+ * initialisation options for sorting, which is why it is not already baked in
+ */
+ function _fnInvalidate( settings, rowIdx, src, colIdx )
+ {
+ var row = settings.aoData[ rowIdx ];
+ var i, ien;
+ var cellWrite = function ( cell, col ) {
+ // This is very frustrating, but in IE if you just write directly
+ // to innerHTML, and elements that are overwritten are GC'ed,
+ // even if there is a reference to them elsewhere
+ while ( cell.childNodes.length ) {
+ cell.removeChild( cell.firstChild );
+ }
+
+ cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' );
+ };
+
+ // Are we reading last data from DOM or the data object?
+ if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) {
+ // Read the data from the DOM
+ row._aData = _fnGetRowElements(
+ settings, row, colIdx, colIdx === undefined ? undefined : row._aData
+ )
+ .data;
+ }
+ else {
+ // Reading from data object, update the DOM
+ var cells = row.anCells;
+
+ if ( cells ) {
+ if ( colIdx !== undefined ) {
+ cellWrite( cells[colIdx], colIdx );
+ }
+ else {
+ for ( i=0, ien=cells.length ; i').appendTo( thead );
+ }
+
+ for ( i=0, ien=columns.length ; itr').attr('role', 'row');
+
+ /* Deal with the footer - add classes if required */
+ $(thead).find('>tr>th, >tr>td').addClass( classes.sHeaderTH );
+ $(tfoot).find('>tr>th, >tr>td').addClass( classes.sFooterTH );
+
+ // Cache the footer cells. Note that we only take the cells from the first
+ // row in the footer. If there is more than one row the user wants to
+ // interact with, they need to use the table().foot() method. Note also this
+ // allows cells to be used for multiple columns using colspan
+ if ( tfoot !== null ) {
+ var cells = oSettings.aoFooter[0];
+
+ for ( i=0, ien=cells.length ; i=0 ; j-- )
+ {
+ if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden )
+ {
+ aoLocal[i].splice( j, 1 );
+ }
+ }
+
+ /* Prep the applied array - it needs an element for each row */
+ aApplied.push( [] );
+ }
+
+ for ( i=0, iLen=aoLocal.length ; i= oSettings.fnRecordsDisplay() ?
+ 0 :
+ iInitDisplayStart;
+
+ oSettings.iInitDisplayStart = -1;
+ }
+
+ var iDisplayStart = oSettings._iDisplayStart;
+ var iDisplayEnd = oSettings.fnDisplayEnd();
+
+ /* Server-side processing draw intercept */
+ if ( oSettings.bDeferLoading )
+ {
+ oSettings.bDeferLoading = false;
+ oSettings.iDraw++;
+ _fnProcessingDisplay( oSettings, false );
+ }
+ else if ( !bServerSide )
+ {
+ oSettings.iDraw++;
+ }
+ else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) )
+ {
+ return;
+ }
+
+ if ( aiDisplay.length !== 0 )
+ {
+ var iStart = bServerSide ? 0 : iDisplayStart;
+ var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd;
+
+ for ( var j=iStart ; j ', { 'class': iStripes ? asStripeClasses[0] : '' } )
+ .append( $(' ', {
+ 'valign': 'top',
+ 'colSpan': _fnVisbleColumns( oSettings ),
+ 'class': oSettings.oClasses.sRowEmpty
+ } ).html( sZero ) )[0];
+ }
+
+ /* Header and footer callbacks */
+ _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0],
+ _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
+
+ _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0],
+ _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
+
+ var body = $(oSettings.nTBody);
+
+ body.children().detach();
+ body.append( $(anRows) );
+
+ /* Call all required callback functions for the end of a draw */
+ _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] );
+
+ /* Draw is complete, sorting and filtering must be as well */
+ oSettings.bSorted = false;
+ oSettings.bFiltered = false;
+ oSettings.bDrawing = false;
+ }
+
+
+ /**
+ * Redraw the table - taking account of the various features which are enabled
+ * @param {object} oSettings dataTables settings object
+ * @param {boolean} [holdPosition] Keep the current paging position. By default
+ * the paging is reset to the first page
+ * @memberof DataTable#oApi
+ */
+ function _fnReDraw( settings, holdPosition )
+ {
+ var
+ features = settings.oFeatures,
+ sort = features.bSort,
+ filter = features.bFilter;
+
+ if ( sort ) {
+ _fnSort( settings );
+ }
+
+ if ( filter ) {
+ _fnFilterComplete( settings, settings.oPreviousSearch );
+ }
+ else {
+ // No filtering, so we want to just use the display master
+ settings.aiDisplay = settings.aiDisplayMaster.slice();
+ }
+
+ if ( holdPosition !== true ) {
+ settings._iDisplayStart = 0;
+ }
+
+ // Let any modules know about the draw hold position state (used by
+ // scrolling internally)
+ settings._drawHold = holdPosition;
+
+ _fnDraw( settings );
+
+ settings._drawHold = false;
+ }
+
+
+ /**
+ * Add the options to the page HTML for the table
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnAddOptionsHtml ( oSettings )
+ {
+ var classes = oSettings.oClasses;
+ var table = $(oSettings.nTable);
+ var holding = $('
').insertBefore( table ); // Holding element for speed
+ var features = oSettings.oFeatures;
+
+ // All DataTables are wrapped in a div
+ var insert = $('
', {
+ id: oSettings.sTableId+'_wrapper',
+ 'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' '+classes.sNoFooter)
+ } );
+
+ oSettings.nHolding = holding[0];
+ oSettings.nTableWrapper = insert[0];
+ oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling;
+
+ /* Loop over the user set positioning and place the elements as needed */
+ var aDom = oSettings.sDom.split('');
+ var featureNode, cOption, nNewNode, cNext, sAttr, j;
+ for ( var i=0 ; i')[0];
+
+ /* Check to see if we should append an id and/or a class name to the container */
+ cNext = aDom[i+1];
+ if ( cNext == "'" || cNext == '"' )
+ {
+ sAttr = "";
+ j = 2;
+ while ( aDom[i+j] != cNext )
+ {
+ sAttr += aDom[i+j];
+ j++;
+ }
+
+ /* Replace jQuery UI constants @todo depreciated */
+ if ( sAttr == "H" )
+ {
+ sAttr = classes.sJUIHeader;
+ }
+ else if ( sAttr == "F" )
+ {
+ sAttr = classes.sJUIFooter;
+ }
+
+ /* The attribute can be in the format of "#id.class", "#id" or "class" This logic
+ * breaks the string into parts and applies them as needed
+ */
+ if ( sAttr.indexOf('.') != -1 )
+ {
+ var aSplit = sAttr.split('.');
+ nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1);
+ nNewNode.className = aSplit[1];
+ }
+ else if ( sAttr.charAt(0) == "#" )
+ {
+ nNewNode.id = sAttr.substr(1, sAttr.length-1);
+ }
+ else
+ {
+ nNewNode.className = sAttr;
+ }
+
+ i += j; /* Move along the position array */
+ }
+
+ insert.append( nNewNode );
+ insert = $(nNewNode);
+ }
+ else if ( cOption == '>' )
+ {
+ /* End container div */
+ insert = insert.parent();
+ }
+ // @todo Move options into their own plugins?
+ else if ( cOption == 'l' && features.bPaginate && features.bLengthChange )
+ {
+ /* Length */
+ featureNode = _fnFeatureHtmlLength( oSettings );
+ }
+ else if ( cOption == 'f' && features.bFilter )
+ {
+ /* Filter */
+ featureNode = _fnFeatureHtmlFilter( oSettings );
+ }
+ else if ( cOption == 'r' && features.bProcessing )
+ {
+ /* pRocessing */
+ featureNode = _fnFeatureHtmlProcessing( oSettings );
+ }
+ else if ( cOption == 't' )
+ {
+ /* Table */
+ featureNode = _fnFeatureHtmlTable( oSettings );
+ }
+ else if ( cOption == 'i' && features.bInfo )
+ {
+ /* Info */
+ featureNode = _fnFeatureHtmlInfo( oSettings );
+ }
+ else if ( cOption == 'p' && features.bPaginate )
+ {
+ /* Pagination */
+ featureNode = _fnFeatureHtmlPaginate( oSettings );
+ }
+ else if ( DataTable.ext.feature.length !== 0 )
+ {
+ /* Plug-in features */
+ var aoFeatures = DataTable.ext.feature;
+ for ( var k=0, kLen=aoFeatures.length ; k data[old]
+ var compat = function ( old, modern ) {
+// return json[old] !== undefined ? json[old] : json[modern];
+ return data[old] !== undefined ? data[old] : data[modern];
+ };
+
+ var data = _fnAjaxDataSrc( settings, json );
+ var draw = compat( 'sEcho', 'draw' );
+ var recordsTotal = compat( 'iTotalRecords', 'recordsTotal' );
+ var recordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' );
+
+ if ( draw ) {
+ // Protect against out of sequence returns
+// if ( draw*1 < settings.iDraw ) {
+// return;
+// }
+ settings.iDraw = draw * 1;
+ }
+
+ _fnClearTable( settings );
+ settings._iRecordsTotal = parseInt(recordsTotal, 10);
+ settings._iRecordsDisplay = parseInt(recordsFiltered, 10);
+
+ //VFloros' interference, data --> data.data
+
+ data = data.data;//vf
+ for ( var i=0, ien=data.length ; i ';
+
+ var str = language.sSearch;
+ str = str.match(/_INPUT_/) ?
+ str.replace('_INPUT_', input) :
+ str+input;
+
+ var filter = $('
', {
+ 'id': ! features.f ? tableId+'_filter' : null,
+ 'class': classes.sFilter
+ } )
+ .append( $(' ' ).append( str ) );
+
+ var searchFn = function() {
+ /* Update all other filter input elements for the new display */
+ var n = features.f;
+ var val = !this.value ? "" : this.value; // mental IE8 fix :-(
+
+ /* Now do the filter */
+ if ( val != previousSearch.sSearch ) {
+ _fnFilterComplete( settings, {
+ "sSearch": val,
+ "bRegex": previousSearch.bRegex,
+ "bSmart": previousSearch.bSmart ,
+ "bCaseInsensitive": previousSearch.bCaseInsensitive
+ } );
+
+ // Need to redraw, without resorting
+ settings._iDisplayStart = 0;
+ _fnDraw( settings );
+ }
+ };
+
+ var searchDelay = settings.searchDelay !== null ?
+ settings.searchDelay :
+ _fnDataSource( settings ) === 'ssp' ?
+ 400 :
+ 0;
+
+ var jqFilter = $('input', filter)
+ .val( previousSearch.sSearch )
+ .attr( 'placeholder', language.sSearchPlaceholder )
+ .bind(
+ 'keyup.DT search.DT input.DT paste.DT cut.DT',
+ searchDelay ?
+ _fnThrottle( searchFn, searchDelay ) :
+ searchFn
+ )
+ .bind( 'keypress.DT', function(e) {
+ /* Prevent form submission */
+ if ( e.keyCode == 13 ) {
+ return false;
+ }
+ } )
+ .attr('aria-controls', tableId);
+
+ // Update the input elements whenever the table is filtered
+ $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) {
+ if ( settings === s ) {
+ // IE9 throws an 'unknown error' if document.activeElement is used
+ // inside an iframe or frame...
+ try {
+ if ( jqFilter[0] !== document.activeElement ) {
+ jqFilter.val( previousSearch.sSearch );
+ }
+ }
+ catch ( e ) {}
+ }
+ } );
+
+ return filter[0];
+ }
+
+
+ /**
+ * Filter the table using both the global filter and column based filtering
+ * @param {object} oSettings dataTables settings object
+ * @param {object} oSearch search information
+ * @param {int} [iForce] force a research of the master array (1) or not (undefined or 0)
+ * @memberof DataTable#oApi
+ */
+ function _fnFilterComplete ( oSettings, oInput, iForce )
+ {
+ var oPrevSearch = oSettings.oPreviousSearch;
+ var aoPrevSearch = oSettings.aoPreSearchCols;
+ var fnSaveFilter = function ( oFilter ) {
+ /* Save the filtering values */
+ oPrevSearch.sSearch = oFilter.sSearch;
+ oPrevSearch.bRegex = oFilter.bRegex;
+ oPrevSearch.bSmart = oFilter.bSmart;
+ oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive;
+ };
+ var fnRegex = function ( o ) {
+ // Backwards compatibility with the bEscapeRegex option
+ return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex;
+ };
+
+ // Resolve any column types that are unknown due to addition or invalidation
+ // @todo As per sort - can this be moved into an event handler?
+ _fnColumnTypes( oSettings );
+
+ /* In server-side processing all filtering is done by the server, so no point hanging around here */
+ if ( _fnDataSource( oSettings ) != 'ssp' )
+ {
+ /* Global filter */
+ _fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive );
+ fnSaveFilter( oInput );
+
+ /* Now do the individual column filter */
+ for ( var i=0 ; i=0 ; i-- ) {
+ data = settings.aoData[ display[i] ]._aFilterData[ colIdx ];
+
+ if ( ! rpSearch.test( data ) ) {
+ display.splice( i, 1 );
+ }
+ }
+ }
+
+
+ /**
+ * Filter the data table based on user input and draw the table
+ * @param {object} settings dataTables settings object
+ * @param {string} input string to filter on
+ * @param {int} force optional - force a research of the master array (1) or not (undefined or 0)
+ * @param {bool} regex treat as a regular expression or not
+ * @param {bool} smart perform smart filtering or not
+ * @param {bool} caseInsensitive Do case insenstive matching or not
+ * @memberof DataTable#oApi
+ */
+ function _fnFilter( settings, input, force, regex, smart, caseInsensitive )
+ {
+ var rpSearch = _fnFilterCreateSearch( input, regex, smart, caseInsensitive );
+ var prevSearch = settings.oPreviousSearch.sSearch;
+ var displayMaster = settings.aiDisplayMaster;
+ var display, invalidated, i;
+
+ // Need to take account of custom filtering functions - always filter
+ if ( DataTable.ext.search.length !== 0 ) {
+ force = true;
+ }
+
+ // Check if any of the rows were invalidated
+ invalidated = _fnFilterData( settings );
+
+ // If the input is blank - we just want the full data set
+ if ( input.length <= 0 ) {
+ settings.aiDisplay = displayMaster.slice();
+ }
+ else {
+ // New search - start from the master array
+ if ( invalidated ||
+ force ||
+ prevSearch.length > input.length ||
+ input.indexOf(prevSearch) !== 0 ||
+ settings.bSorted // On resort, the display master needs to be
+ // re-filtered since indexes will have changed
+ ) {
+ settings.aiDisplay = displayMaster.slice();
+ }
+
+ // Search the display array
+ display = settings.aiDisplay;
+
+ for ( i=display.length-1 ; i>=0 ; i-- ) {
+ if ( ! rpSearch.test( settings.aoData[ display[i] ]._sFilterRow ) ) {
+ display.splice( i, 1 );
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Build a regular expression object suitable for searching a table
+ * @param {string} sSearch string to search for
+ * @param {bool} bRegex treat as a regular expression or not
+ * @param {bool} bSmart perform smart filtering or not
+ * @param {bool} bCaseInsensitive Do case insensitive matching or not
+ * @returns {RegExp} constructed object
+ * @memberof DataTable#oApi
+ */
+ function _fnFilterCreateSearch( search, regex, smart, caseInsensitive )
+ {
+ search = regex ?
+ search :
+ _fnEscapeRegex( search );
+
+ if ( smart ) {
+ /* For smart filtering we want to allow the search to work regardless of
+ * word order. We also want double quoted text to be preserved, so word
+ * order is important - a la google. So this is what we want to
+ * generate:
+ *
+ * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$
+ */
+ var a = $.map( search.match( /"[^"]+"|[^ ]+/g ) || [''], function ( word ) {
+ if ( word.charAt(0) === '"' ) {
+ var m = word.match( /^"(.*)"$/ );
+ word = m ? m[1] : word;
+ }
+
+ return word.replace('"', '');
+ } );
+
+ search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$';
+ }
+
+ return new RegExp( search, caseInsensitive ? 'i' : '' );
+ }
+
+
+ /**
+ * Escape a string such that it can be used in a regular expression
+ * @param {string} sVal string to escape
+ * @returns {string} escaped string
+ * @memberof DataTable#oApi
+ */
+ function _fnEscapeRegex ( sVal )
+ {
+ return sVal.replace( _re_escape_regex, '\\$1' );
+ }
+
+
+
+ var __filter_div = $('')[0];
+ var __filter_div_textContent = __filter_div.textContent !== undefined;
+
+ // Update the filtering data for each row if needed (by invalidation or first run)
+ function _fnFilterData ( settings )
+ {
+ var columns = settings.aoColumns;
+ var column;
+ var i, j, ien, jen, filterData, cellData, row;
+ var fomatters = DataTable.ext.type.search;
+ var wasInvalidated = false;
+
+ for ( i=0, ien=settings.aoData.length ; i', {
+ 'class': settings.oClasses.sInfo,
+ 'id': ! nodes ? tid+'_info' : null
+ } );
+
+ if ( ! nodes ) {
+ // Update display on each draw
+ settings.aoDrawCallback.push( {
+ "fn": _fnUpdateInfo,
+ "sName": "information"
+ } );
+
+ n
+ .attr( 'role', 'status' )
+ .attr( 'aria-live', 'polite' );
+
+ // Table is described by our info div
+ $(settings.nTable).attr( 'aria-describedby', tid+'_info' );
+ }
+
+ return n[0];
+ }
+
+
+ /**
+ * Update the information elements in the display
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnUpdateInfo ( settings )
+ {
+ /* Show information about the table */
+ var nodes = settings.aanFeatures.i;
+ if ( nodes.length === 0 ) {
+ return;
+ }
+
+ var
+ lang = settings.oLanguage,
+ start = settings._iDisplayStart+1,
+ end = settings.fnDisplayEnd(),
+ max = settings.fnRecordsTotal(),
+ total = settings.fnRecordsDisplay(),
+ out = total ?
+ lang.sInfo :
+ lang.sInfoEmpty;
+
+ if ( total !== max ) {
+ /* Record set after filtering */
+ out += ' ' + lang.sInfoFiltered;
+ }
+
+ // Convert the macros
+ out += lang.sInfoPostFix;
+ out = _fnInfoMacros( settings, out );
+
+ var callback = lang.fnInfoCallback;
+ if ( callback !== null ) {
+ out = callback.call( settings.oInstance,
+ settings, start, end, max, total, out
+ );
+ }
+
+ $(nodes).html( out );
+ }
+
+
+ function _fnInfoMacros ( settings, str )
+ {
+ // When infinite scrolling, we are always starting at 1. _iDisplayStart is used only
+ // internally
+ var
+ formatter = settings.fnFormatNumber,
+ start = settings._iDisplayStart+1,
+ len = settings._iDisplayLength,
+ vis = settings.fnRecordsDisplay(),
+ all = len === -1;
+
+ return str.
+ replace(/_START_/g, formatter.call( settings, start ) ).
+ replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ).
+ replace(/_MAX_/g, formatter.call( settings, settings.fnRecordsTotal() ) ).
+ replace(/_TOTAL_/g, formatter.call( settings, vis ) ).
+ replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ).
+ replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) );
+ }
+
+
+
+ /**
+ * Draw the table for the first time, adding all required features
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnInitialise ( settings )
+ {
+ var i, iLen, iAjaxStart=settings.iInitDisplayStart;
+ var columns = settings.aoColumns, column;
+ var features = settings.oFeatures;
+ var deferLoading = settings.bDeferLoading; // value modified by the draw
+
+ /* Ensure that the table data is fully initialised */
+ if ( ! settings.bInitialised ) {
+ setTimeout( function(){ _fnInitialise( settings ); }, 200 );
+ return;
+ }
+
+ /* Show the display HTML options */
+ _fnAddOptionsHtml( settings );
+
+ /* Build and draw the header / footer for the table */
+ _fnBuildHead( settings );
+ _fnDrawHead( settings, settings.aoHeader );
+ _fnDrawHead( settings, settings.aoFooter );
+
+ /* Okay to show that something is going on now */
+ _fnProcessingDisplay( settings, true );
+
+ /* Calculate sizes for columns */
+ if ( features.bAutoWidth ) {
+ _fnCalculateColumnWidths( settings );
+ }
+
+ for ( i=0, iLen=columns.length ; i', {
+ 'name': tableId+'_length',
+ 'aria-controls': tableId,
+ 'class': classes.sLengthSelect
+ } );
+
+ for ( var i=0, ien=lengths.length ; i
').addClass( classes.sLength );
+ if ( ! settings.aanFeatures.l ) {
+ div[0].id = tableId+'_length';
+ }
+
+ div.children().append(
+ settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML )
+ );
+
+ // Can't use `select` variable as user might provide their own and the
+ // reference is broken by the use of outerHTML
+ $('select', div)
+ .val( settings._iDisplayLength )
+ .bind( 'change.DT', function(e) {
+ _fnLengthChange( settings, $(this).val() );
+ _fnDraw( settings );
+ } );
+
+ // Update node value whenever anything changes the table's length
+ $(settings.nTable).bind( 'length.dt.DT', function (e, s, len) {
+ if ( settings === s ) {
+ $('select', div).val( len );
+ }
+ } );
+
+ return div[0];
+ }
+
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Note that most of the paging logic is done in
+ * DataTable.ext.pager
+ */
+
+ /**
+ * Generate the node required for default pagination
+ * @param {object} oSettings dataTables settings object
+ * @returns {node} Pagination feature node
+ * @memberof DataTable#oApi
+ */
+ function _fnFeatureHtmlPaginate ( settings )
+ {
+ var
+ type = settings.sPaginationType,
+ plugin = DataTable.ext.pager[ type ],
+ modern = typeof plugin === 'function',
+ redraw = function( settings ) {
+ _fnDraw( settings );
+ },
+ node = $('
').addClass( settings.oClasses.sPaging + type )[0],
+ features = settings.aanFeatures;
+
+ if ( ! modern ) {
+ plugin.fnInit( settings, node, redraw );
+ }
+
+ /* Add a draw callback for the pagination on first instance, to update the paging display */
+ if ( ! features.p )
+ {
+ node.id = settings.sTableId+'_paginate';
+
+ settings.aoDrawCallback.push( {
+ "fn": function( settings ) {
+ if ( modern ) {
+ var
+ start = settings._iDisplayStart,
+ len = settings._iDisplayLength,
+ visRecords = settings.fnRecordsDisplay(),
+ all = len === -1,
+ page = all ? 0 : Math.ceil( start / len ),
+ pages = all ? 1 : Math.ceil( visRecords / len ),
+ buttons = plugin(page, pages),
+ i, ien;
+
+ for ( i=0, ien=features.p.length ; i records )
+ {
+ start = 0;
+ }
+ }
+ else if ( action == "first" )
+ {
+ start = 0;
+ }
+ else if ( action == "previous" )
+ {
+ start = len >= 0 ?
+ start - len :
+ 0;
+
+ if ( start < 0 )
+ {
+ start = 0;
+ }
+ }
+ else if ( action == "next" )
+ {
+ if ( start + len < records )
+ {
+ start += len;
+ }
+ }
+ else if ( action == "last" )
+ {
+ start = Math.floor( (records-1) / len) * len;
+ }
+ else
+ {
+ _fnLog( settings, 0, "Unknown paging action: "+action, 5 );
+ }
+
+ var changed = settings._iDisplayStart !== start;
+ settings._iDisplayStart = start;
+
+ if ( changed ) {
+ _fnCallbackFire( settings, null, 'page', [settings] );
+
+ if ( redraw ) {
+ _fnDraw( settings );
+ }
+ }
+
+ return changed;
+ }
+
+
+
+ /**
+ * Generate the node required for the processing node
+ * @param {object} settings dataTables settings object
+ * @returns {node} Processing element
+ * @memberof DataTable#oApi
+ */
+ function _fnFeatureHtmlProcessing ( settings )
+ {
+ return $('
', {
+ 'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null,
+ 'class': settings.oClasses.sProcessing
+ } )
+ .html( settings.oLanguage.sProcessing )
+ .insertBefore( settings.nTable )[0];
+ }
+
+
+ /**
+ * Display or hide the processing indicator
+ * @param {object} settings dataTables settings object
+ * @param {bool} show Show the processing indicator (true) or not (false)
+ * @memberof DataTable#oApi
+ */
+ function _fnProcessingDisplay ( settings, show )
+ {
+ if ( settings.oFeatures.bProcessing ) {
+ $(settings.aanFeatures.r).css( 'display', show ? 'block' : 'none' );
+ }
+
+ _fnCallbackFire( settings, null, 'processing', [settings, show] );
+ }
+
+ /**
+ * Add any control elements for the table - specifically scrolling
+ * @param {object} settings dataTables settings object
+ * @returns {node} Node to add to the DOM
+ * @memberof DataTable#oApi
+ */
+ function _fnFeatureHtmlTable ( settings )
+ {
+ var table = $(settings.nTable);
+
+ // Add the ARIA grid role to the table
+ table.attr( 'role', 'grid' );
+
+ // Scrolling from here on in
+ var scroll = settings.oScroll;
+
+ if ( scroll.sX === '' && scroll.sY === '' ) {
+ return settings.nTable;
+ }
+
+ var scrollX = scroll.sX;
+ var scrollY = scroll.sY;
+ var classes = settings.oClasses;
+ var caption = table.children('caption');
+ var captionSide = caption.length ? caption[0]._captionSide : null;
+ var headerClone = $( table[0].cloneNode(false) );
+ var footerClone = $( table[0].cloneNode(false) );
+ var footer = table.children('tfoot');
+ var _div = '
';
+ var size = function ( s ) {
+ return !s ? null : _fnStringToCss( s );
+ };
+
+ // This is fairly messy, but with x scrolling enabled, if the table has a
+ // width attribute, regardless of any width applied using the column width
+ // options, the browser will shrink or grow the table as needed to fit into
+ // that 100%. That would make the width options useless. So we remove it.
+ // This is okay, under the assumption that width:100% is applied to the
+ // table in CSS (it is in the default stylesheet) which will set the table
+ // width as appropriate (the attribute and css behave differently...)
+ if ( scroll.sX && table.attr('width') === '100%' ) {
+ table.removeAttr('width');
+ }
+
+ if ( ! footer.length ) {
+ footer = null;
+ }
+
+ /*
+ * The HTML structure that we want to generate in this function is:
+ * div - scroller
+ * div - scroll head
+ * div - scroll head inner
+ * table - scroll head table
+ * thead - thead
+ * div - scroll body
+ * table - table (master table)
+ * thead - thead clone for sizing
+ * tbody - tbody
+ * div - scroll foot
+ * div - scroll foot inner
+ * table - scroll foot table
+ * tfoot - tfoot
+ */
+ var scroller = $( _div, { 'class': classes.sScrollWrapper } )
+ .append(
+ $(_div, { 'class': classes.sScrollHead } )
+ .css( {
+ overflow: 'hidden',
+ position: 'relative',
+ border: 0,
+ width: scrollX ? size(scrollX) : '100%'
+ } )
+ .append(
+ $(_div, { 'class': classes.sScrollHeadInner } )
+ .css( {
+ 'box-sizing': 'content-box',
+ width: scroll.sXInner || '100%'
+ } )
+ .append(
+ headerClone
+ .removeAttr('id')
+ .css( 'margin-left', 0 )
+ .append( captionSide === 'top' ? caption : null )
+ .append(
+ table.children('thead')
+ )
+ )
+ )
+ )
+ .append(
+ $(_div, { 'class': classes.sScrollBody } )
+ .css( {
+ position: 'relative',
+ overflow: 'auto',
+ width: size( scrollX )
+ } )
+ .append( table )
+ );
+
+ if ( footer ) {
+ scroller.append(
+ $(_div, { 'class': classes.sScrollFoot } )
+ .css( {
+ overflow: 'hidden',
+ border: 0,
+ width: scrollX ? size(scrollX) : '100%'
+ } )
+ .append(
+ $(_div, { 'class': classes.sScrollFootInner } )
+ .append(
+ footerClone
+ .removeAttr('id')
+ .css( 'margin-left', 0 )
+ .append( captionSide === 'bottom' ? caption : null )
+ .append(
+ table.children('tfoot')
+ )
+ )
+ )
+ );
+ }
+
+ var children = scroller.children();
+ var scrollHead = children[0];
+ var scrollBody = children[1];
+ var scrollFoot = footer ? children[2] : null;
+
+ // When the body is scrolled, then we also want to scroll the headers
+ if ( scrollX ) {
+ $(scrollBody).on( 'scroll.DT', function (e) {
+ var scrollLeft = this.scrollLeft;
+
+ scrollHead.scrollLeft = scrollLeft;
+
+ if ( footer ) {
+ scrollFoot.scrollLeft = scrollLeft;
+ }
+ } );
+ }
+
+ $(scrollBody).css(
+ scrollY && scroll.bCollapse ? 'max-height' : 'height',
+ scrollY
+ );
+
+ settings.nScrollHead = scrollHead;
+ settings.nScrollBody = scrollBody;
+ settings.nScrollFoot = scrollFoot;
+
+ // On redraw - align columns
+ settings.aoDrawCallback.push( {
+ "fn": _fnScrollDraw,
+ "sName": "scrolling"
+ } );
+
+ return scroller[0];
+ }
+
+
+
+ /**
+ * Update the header, footer and body tables for resizing - i.e. column
+ * alignment.
+ *
+ * Welcome to the most horrible function DataTables. The process that this
+ * function follows is basically:
+ * 1. Re-create the table inside the scrolling div
+ * 2. Take live measurements from the DOM
+ * 3. Apply the measurements to align the columns
+ * 4. Clean up
+ *
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnScrollDraw ( settings )
+ {
+ // Given that this is such a monster function, a lot of variables are use
+ // to try and keep the minimised size as small as possible
+ var
+ scroll = settings.oScroll,
+ scrollX = scroll.sX,
+ scrollXInner = scroll.sXInner,
+ scrollY = scroll.sY,
+ barWidth = scroll.iBarWidth,
+ divHeader = $(settings.nScrollHead),
+ divHeaderStyle = divHeader[0].style,
+ divHeaderInner = divHeader.children('div'),
+ divHeaderInnerStyle = divHeaderInner[0].style,
+ divHeaderTable = divHeaderInner.children('table'),
+ divBodyEl = settings.nScrollBody,
+ divBody = $(divBodyEl),
+ divBodyStyle = divBodyEl.style,
+ divFooter = $(settings.nScrollFoot),
+ divFooterInner = divFooter.children('div'),
+ divFooterTable = divFooterInner.children('table'),
+ header = $(settings.nTHead),
+ table = $(settings.nTable),
+ tableEl = table[0],
+ tableStyle = tableEl.style,
+ footer = settings.nTFoot ? $(settings.nTFoot) : null,
+ browser = settings.oBrowser,
+ ie67 = browser.bScrollOversize,
+ headerTrgEls, footerTrgEls,
+ headerSrcEls, footerSrcEls,
+ headerCopy, footerCopy,
+ headerWidths=[], footerWidths=[],
+ headerContent=[],
+ idx, correction, sanityWidth,
+ zeroOut = function(nSizer) {
+ var style = nSizer.style;
+ style.paddingTop = "0";
+ style.paddingBottom = "0";
+ style.borderTopWidth = "0";
+ style.borderBottomWidth = "0";
+ style.height = 0;
+ };
+
+ /*
+ * 1. Re-create the table inside the scrolling div
+ */
+
+ // Remove the old minimised thead and tfoot elements in the inner table
+ table.children('thead, tfoot').remove();
+
+ // Clone the current header and footer elements and then place it into the inner table
+ headerCopy = header.clone().prependTo( table );
+ headerTrgEls = header.find('tr'); // original header is in its own table
+ headerSrcEls = headerCopy.find('tr');
+ headerCopy.find('th, td').removeAttr('tabindex');
+
+ if ( footer ) {
+ footerCopy = footer.clone().prependTo( table );
+ footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized
+ footerSrcEls = footerCopy.find('tr');
+ }
+
+
+ /*
+ * 2. Take live measurements from the DOM - do not alter the DOM itself!
+ */
+
+ // Remove old sizing and apply the calculated column widths
+ // Get the unique column headers in the newly created (cloned) header. We want to apply the
+ // calculated sizes to this header
+ if ( ! scrollX )
+ {
+ divBodyStyle.width = '100%';
+ divHeader[0].style.width = '100%';
+ }
+
+ $.each( _fnGetUniqueThs( settings, headerCopy ), function ( i, el ) {
+ idx = _fnVisibleToColumnIndex( settings, i );
+ el.style.width = settings.aoColumns[idx].sWidth;
+ } );
+
+ if ( footer ) {
+ _fnApplyToChildren( function(n) {
+ n.style.width = "";
+ }, footerSrcEls );
+ }
+
+ // Size the table as a whole
+ sanityWidth = table.outerWidth();
+ if ( scrollX === "" ) {
+ // No x scrolling
+ tableStyle.width = "100%";
+
+ // IE7 will make the width of the table when 100% include the scrollbar
+ // - which is shouldn't. When there is a scrollbar we need to take this
+ // into account.
+ if ( ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight ||
+ divBody.css('overflow-y') == "scroll")
+ ) {
+ tableStyle.width = _fnStringToCss( table.outerWidth() - barWidth);
+ }
+
+ // Recalculate the sanity width
+ sanityWidth = table.outerWidth();
+ }
+ else if ( scrollXInner !== "" ) {
+ // legacy x scroll inner has been given - use it
+ tableStyle.width = _fnStringToCss(scrollXInner);
+
+ // Recalculate the sanity width
+ sanityWidth = table.outerWidth();
+ }
+
+ // Hidden header should have zero height, so remove padding and borders. Then
+ // set the width based on the real headers
+
+ // Apply all styles in one pass
+ _fnApplyToChildren( zeroOut, headerSrcEls );
+
+ // Read all widths in next pass
+ _fnApplyToChildren( function(nSizer) {
+ headerContent.push( nSizer.innerHTML );
+ headerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
+ }, headerSrcEls );
+
+ // Apply all widths in final pass
+ _fnApplyToChildren( function(nToSize, i) {
+ nToSize.style.width = headerWidths[i];
+ }, headerTrgEls );
+
+ $(headerSrcEls).height(0);
+
+ /* Same again with the footer if we have one */
+ if ( footer )
+ {
+ _fnApplyToChildren( zeroOut, footerSrcEls );
+
+ _fnApplyToChildren( function(nSizer) {
+ footerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
+ }, footerSrcEls );
+
+ _fnApplyToChildren( function(nToSize, i) {
+ nToSize.style.width = footerWidths[i];
+ }, footerTrgEls );
+
+ $(footerSrcEls).height(0);
+ }
+
+
+ /*
+ * 3. Apply the measurements
+ */
+
+ // "Hide" the header and footer that we used for the sizing. We need to keep
+ // the content of the cell so that the width applied to the header and body
+ // both match, but we want to hide it completely. We want to also fix their
+ // width to what they currently are
+ _fnApplyToChildren( function(nSizer, i) {
+ nSizer.innerHTML = ''+headerContent[i]+'
';
+ nSizer.style.width = headerWidths[i];
+ }, headerSrcEls );
+
+ if ( footer )
+ {
+ _fnApplyToChildren( function(nSizer, i) {
+ nSizer.innerHTML = "";
+ nSizer.style.width = footerWidths[i];
+ }, footerSrcEls );
+ }
+
+ // Sanity check that the table is of a sensible width. If not then we are going to get
+ // misalignment - try to prevent this by not allowing the table to shrink below its min width
+ if ( table.outerWidth() < sanityWidth )
+ {
+ // The min width depends upon if we have a vertical scrollbar visible or not */
+ correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight ||
+ divBody.css('overflow-y') == "scroll")) ?
+ sanityWidth+barWidth :
+ sanityWidth;
+
+ // IE6/7 are a law unto themselves...
+ if ( ie67 && (divBodyEl.scrollHeight >
+ divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll")
+ ) {
+ tableStyle.width = _fnStringToCss( correction-barWidth );
+ }
+
+ // And give the user a warning that we've stopped the table getting too small
+ if ( scrollX === "" || scrollXInner !== "" ) {
+ _fnLog( settings, 1, 'Possible column misalignment', 6 );
+ }
+ }
+ else
+ {
+ correction = '100%';
+ }
+
+ // Apply to the container elements
+ divBodyStyle.width = _fnStringToCss( correction );
+ divHeaderStyle.width = _fnStringToCss( correction );
+
+ if ( footer ) {
+ settings.nScrollFoot.style.width = _fnStringToCss( correction );
+ }
+
+
+ /*
+ * 4. Clean up
+ */
+ if ( ! scrollY ) {
+ /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting
+ * the scrollbar height from the visible display, rather than adding it on. We need to
+ * set the height in order to sort this. Don't want to do it in any other browsers.
+ */
+ if ( ie67 ) {
+ divBodyStyle.height = _fnStringToCss( tableEl.offsetHeight+barWidth );
+ }
+ }
+
+ /* Finally set the width's of the header and footer tables */
+ var iOuterWidth = table.outerWidth();
+ divHeaderTable[0].style.width = _fnStringToCss( iOuterWidth );
+ divHeaderInnerStyle.width = _fnStringToCss( iOuterWidth );
+
+ // Figure out if there are scrollbar present - if so then we need a the header and footer to
+ // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar)
+ var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll";
+ var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' );
+ divHeaderInnerStyle[ padding ] = bScrolling ? barWidth+"px" : "0px";
+
+ if ( footer ) {
+ divFooterTable[0].style.width = _fnStringToCss( iOuterWidth );
+ divFooterInner[0].style.width = _fnStringToCss( iOuterWidth );
+ divFooterInner[0].style[padding] = bScrolling ? barWidth+"px" : "0px";
+ }
+
+ /* Adjust the position of the header in case we loose the y-scrollbar */
+ divBody.scroll();
+
+ // If sorting or filtering has occurred, jump the scrolling back to the top
+ // only if we aren't holding the position
+ if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) {
+ divBodyEl.scrollTop = 0;
+ }
+ }
+
+
+
+ /**
+ * Apply a given function to the display child nodes of an element array (typically
+ * TD children of TR rows
+ * @param {function} fn Method to apply to the objects
+ * @param array {nodes} an1 List of elements to look through for display children
+ * @param array {nodes} an2 Another list (identical structure to the first) - optional
+ * @memberof DataTable#oApi
+ */
+ function _fnApplyToChildren( fn, an1, an2 )
+ {
+ var index=0, i=0, iLen=an1.length;
+ var nNode1, nNode2;
+
+ while ( i < iLen ) {
+ nNode1 = an1[i].firstChild;
+ nNode2 = an2 ? an2[i].firstChild : null;
+
+ while ( nNode1 ) {
+ if ( nNode1.nodeType === 1 ) {
+ if ( an2 ) {
+ fn( nNode1, nNode2, index );
+ }
+ else {
+ fn( nNode1, index );
+ }
+
+ index++;
+ }
+
+ nNode1 = nNode1.nextSibling;
+ nNode2 = an2 ? nNode2.nextSibling : null;
+ }
+
+ i++;
+ }
+ }
+
+
+
+ var __re_html_remove = /<.*?>/g;
+
+
+ /**
+ * Calculate the width of columns for the table
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnCalculateColumnWidths ( oSettings )
+ {
+ var
+ table = oSettings.nTable,
+ columns = oSettings.aoColumns,
+ scroll = oSettings.oScroll,
+ scrollY = scroll.sY,
+ scrollX = scroll.sX,
+ scrollXInner = scroll.sXInner,
+ columnCount = columns.length,
+ visibleColumns = _fnGetColumns( oSettings, 'bVisible' ),
+ headerCells = $('th', oSettings.nTHead),
+ tableWidthAttr = table.getAttribute('width'), // from DOM element
+ tableContainer = table.parentNode,
+ userInputs = false,
+ i, column, columnIdx, width, outerWidth,
+ browser = oSettings.oBrowser,
+ ie67 = browser.bScrollOversize;
+
+ var styleWidth = table.style.width;
+ if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
+ tableWidthAttr = styleWidth;
+ }
+
+ /* Convert any user input sizes into pixel sizes */
+ for ( i=0 ; i').appendTo( tmpTable.find('tbody') );
+
+ // Clone the table header and footer - we can't use the header / footer
+ // from the cloned table, since if scrolling is active, the table's
+ // real header and footer are contained in different table tags
+ tmpTable.find('thead, tfoot').remove();
+ tmpTable
+ .append( $(oSettings.nTHead).clone() )
+ .append( $(oSettings.nTFoot).clone() );
+
+ // Remove any assigned widths from the footer (from scrolling)
+ tmpTable.find('tfoot th, tfoot td').css('width', '');
+
+ // Apply custom sizing to the cloned header
+ headerCells = _fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] );
+
+ for ( i=0 ; i').css( scrollX || scrollY ?
+ {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ height: 1,
+ right: 0,
+ overflow: 'hidden'
+ } :
+ {}
+ )
+ .append( tmpTable )
+ .appendTo( tableContainer );
+
+ // When scrolling (X or Y) we want to set the width of the table as
+ // appropriate. However, when not scrolling leave the table width as it
+ // is. This results in slightly different, but I think correct behaviour
+ if ( scrollX && scrollXInner ) {
+ tmpTable.width( scrollXInner );
+ }
+ else if ( scrollX ) {
+ tmpTable.css( 'width', 'auto' );
+
+ if ( tmpTable.width() < tableContainer.clientWidth ) {
+ tmpTable.width( tableContainer.clientWidth );
+ }
+ }
+ else if ( scrollY ) {
+ tmpTable.width( tableContainer.clientWidth );
+ }
+ else if ( tableWidthAttr ) {
+ tmpTable.width( tableWidthAttr );
+ }
+
+ // Browsers need a bit of a hand when a width is assigned to any columns
+ // when x-scrolling as they tend to collapse the table to the min-width,
+ // even if we sent the column widths. So we need to keep track of what
+ // the table width should be by summing the user given values, and the
+ // automatic values
+ if ( scrollX )
+ {
+ var total = 0;
+
+ for ( i=0 ; i')
+ .css( 'width', _fnStringToCss( width ) )
+ .appendTo( parent || document.body );
+
+ var val = n[0].offsetWidth;
+ n.remove();
+
+ return val;
+ }
+
+
+ /**
+ * Get the widest node
+ * @param {object} settings dataTables settings object
+ * @param {int} colIdx column of interest
+ * @returns {node} widest table node
+ * @memberof DataTable#oApi
+ */
+ function _fnGetWidestNode( settings, colIdx )
+ {
+ var idx = _fnGetMaxLenString( settings, colIdx );
+ if ( idx < 0 ) {
+ return null;
+ }
+
+ var data = settings.aoData[ idx ];
+ return ! data.nTr ? // Might not have been created when deferred rendering
+ $(' ').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] :
+ data.anCells[ colIdx ];
+ }
+
+
+ /**
+ * Get the maximum strlen for each data column
+ * @param {object} settings dataTables settings object
+ * @param {int} colIdx column of interest
+ * @returns {string} max string length for each column
+ * @memberof DataTable#oApi
+ */
+ function _fnGetMaxLenString( settings, colIdx )
+ {
+ var s, max=-1, maxIdx = -1;
+
+ for ( var i=0, ien=settings.aoData.length ; i max ) {
+ max = s.length;
+ maxIdx = i;
+ }
+ }
+
+ return maxIdx;
+ }
+
+
+ /**
+ * Append a CSS unit (only if required) to a string
+ * @param {string} value to css-ify
+ * @returns {string} value with css unit
+ * @memberof DataTable#oApi
+ */
+ function _fnStringToCss( s )
+ {
+ if ( s === null ) {
+ return '0px';
+ }
+
+ if ( typeof s == 'number' ) {
+ return s < 0 ?
+ '0px' :
+ s+'px';
+ }
+
+ // Check it has a unit character already
+ return s.match(/\d$/) ?
+ s+'px' :
+ s;
+ }
+
+
+
+ function _fnSortFlatten ( settings )
+ {
+ var
+ i, iLen, k, kLen,
+ aSort = [],
+ aiOrig = [],
+ aoColumns = settings.aoColumns,
+ aDataSort, iCol, sType, srcCol,
+ fixed = settings.aaSortingFixed,
+ fixedObj = $.isPlainObject( fixed ),
+ nestedSort = [],
+ add = function ( a ) {
+ if ( a.length && ! $.isArray( a[0] ) ) {
+ // 1D array
+ nestedSort.push( a );
+ }
+ else {
+ // 2D array
+ $.merge( nestedSort, a );
+ }
+ };
+
+ // Build the sort array, with pre-fix and post-fix options if they have been
+ // specified
+ if ( $.isArray( fixed ) ) {
+ add( fixed );
+ }
+
+ if ( fixedObj && fixed.pre ) {
+ add( fixed.pre );
+ }
+
+ add( settings.aaSorting );
+
+ if (fixedObj && fixed.post ) {
+ add( fixed.post );
+ }
+
+ for ( i=0 ; iy ? 1 : 0;
+ if ( test !== 0 ) {
+ return sort.dir === 'asc' ? test : -test;
+ }
+ }
+
+ x = aiOrig[a];
+ y = aiOrig[b];
+ return xy ? 1 : 0;
+ } );
+ }
+ else {
+ // Depreciated - remove in 1.11 (providing a plug-in option)
+ // Not all sort types have formatting methods, so we have to call their sorting
+ // methods.
+ displayMaster.sort( function ( a, b ) {
+ var
+ x, y, k, l, test, sort, fn,
+ len=aSort.length,
+ dataA = aoData[a]._aSortData,
+ dataB = aoData[b]._aSortData;
+
+ for ( k=0 ; ky ? 1 : 0;
+ } );
+ }
+ }
+
+ /* Tell the draw function that we have sorted the data */
+ oSettings.bSorted = true;
+ }
+
+
+ function _fnSortAria ( settings )
+ {
+ var label;
+ var nextSort;
+ var columns = settings.aoColumns;
+ var aSort = _fnSortFlatten( settings );
+ var oAria = settings.oLanguage.oAria;
+
+ // ARIA attributes - need to loop all columns, to update all (removing old
+ // attributes as needed)
+ for ( var i=0, iLen=columns.length ; i/g, "" );
+ var th = col.nTh;
+
+ // IE7 is throwing an error when setting these properties with jQuery's
+ // attr() and removeAttr() methods...
+ th.removeAttribute('aria-sort');
+
+ /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
+ if ( col.bSortable ) {
+ if ( aSort.length > 0 && aSort[0].col == i ) {
+ th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" );
+ nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0];
+ }
+ else {
+ nextSort = asSorting[0];
+ }
+
+ label = sTitle + ( nextSort === "asc" ?
+ oAria.sSortAscending :
+ oAria.sSortDescending
+ );
+ }
+ else {
+ label = sTitle;
+ }
+
+ th.setAttribute('aria-label', label);
+ }
+ }
+
+
+ /**
+ * Function to run on user sort request
+ * @param {object} settings dataTables settings object
+ * @param {node} attachTo node to attach the handler to
+ * @param {int} colIdx column sorting index
+ * @param {boolean} [append=false] Append the requested sort to the existing
+ * sort if true (i.e. multi-column sort)
+ * @param {function} [callback] callback function
+ * @memberof DataTable#oApi
+ */
+ function _fnSortListener ( settings, colIdx, append, callback )
+ {
+ var col = settings.aoColumns[ colIdx ];
+ var sorting = settings.aaSorting;
+ var asSorting = col.asSorting;
+ var nextSortIdx;
+ var next = function ( a, overflow ) {
+ var idx = a._idx;
+ if ( idx === undefined ) {
+ idx = $.inArray( a[1], asSorting );
+ }
+
+ return idx+1 < asSorting.length ?
+ idx+1 :
+ overflow ?
+ null :
+ 0;
+ };
+
+ // Convert to 2D array if needed
+ if ( typeof sorting[0] === 'number' ) {
+ sorting = settings.aaSorting = [ sorting ];
+ }
+
+ // If appending the sort then we are multi-column sorting
+ if ( append && settings.oFeatures.bSortMulti ) {
+ // Are we already doing some kind of sort on this column?
+ var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') );
+
+ if ( sortIdx !== -1 ) {
+ // Yes, modify the sort
+ nextSortIdx = next( sorting[sortIdx], true );
+
+ if ( nextSortIdx === null && sorting.length === 1 ) {
+ nextSortIdx = 0; // can't remove sorting completely
+ }
+
+ if ( nextSortIdx === null ) {
+ sorting.splice( sortIdx, 1 );
+ }
+ else {
+ sorting[sortIdx][1] = asSorting[ nextSortIdx ];
+ sorting[sortIdx]._idx = nextSortIdx;
+ }
+ }
+ else {
+ // No sort on this column yet
+ sorting.push( [ colIdx, asSorting[0], 0 ] );
+ sorting[sorting.length-1]._idx = 0;
+ }
+ }
+ else if ( sorting.length && sorting[0][0] == colIdx ) {
+ // Single column - already sorting on this column, modify the sort
+ nextSortIdx = next( sorting[0] );
+
+ sorting.length = 1;
+ sorting[0][1] = asSorting[ nextSortIdx ];
+ sorting[0]._idx = nextSortIdx;
+ }
+ else {
+ // Single column - sort only on this column
+ sorting.length = 0;
+ sorting.push( [ colIdx, asSorting[0] ] );
+ sorting[0]._idx = 0;
+ }
+
+ // Run the sort by calling a full redraw
+ _fnReDraw( settings );
+
+ // callback used for async user interaction
+ if ( typeof callback == 'function' ) {
+ callback( settings );
+ }
+ }
+
+
+ /**
+ * Attach a sort handler (click) to a node
+ * @param {object} settings dataTables settings object
+ * @param {node} attachTo node to attach the handler to
+ * @param {int} colIdx column sorting index
+ * @param {function} [callback] callback function
+ * @memberof DataTable#oApi
+ */
+ function _fnSortAttachListener ( settings, attachTo, colIdx, callback )
+ {
+ var col = settings.aoColumns[ colIdx ];
+
+ _fnBindAction( attachTo, {}, function (e) {
+ /* If the column is not sortable - don't to anything */
+ if ( col.bSortable === false ) {
+ return;
+ }
+
+ // If processing is enabled use a timeout to allow the processing
+ // display to be shown - otherwise to it synchronously
+ if ( settings.oFeatures.bProcessing ) {
+ _fnProcessingDisplay( settings, true );
+
+ setTimeout( function() {
+ _fnSortListener( settings, colIdx, e.shiftKey, callback );
+
+ // In server-side processing, the draw callback will remove the
+ // processing display
+ if ( _fnDataSource( settings ) !== 'ssp' ) {
+ _fnProcessingDisplay( settings, false );
+ }
+ }, 0 );
+ }
+ else {
+ _fnSortListener( settings, colIdx, e.shiftKey, callback );
+ }
+ } );
+ }
+
+
+ /**
+ * Set the sorting classes on table's body, Note: it is safe to call this function
+ * when bSort and bSortClasses are false
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnSortingClasses( settings )
+ {
+ var oldSort = settings.aLastSort;
+ var sortClass = settings.oClasses.sSortColumn;
+ var sort = _fnSortFlatten( settings );
+ var features = settings.oFeatures;
+ var i, ien, colIdx;
+
+ if ( features.bSort && features.bSortClasses ) {
+ // Remove old sorting classes
+ for ( i=0, ien=oldSort.length ; i 0 && state.time < +new Date() - (duration*1000) ) {
+ return;
+ }
+
+ // Number of columns have changed - all bets are off, no restore of settings
+ if ( columns.length !== state.columns.length ) {
+ return;
+ }
+
+ // Store the saved state so it might be accessed at any time
+ settings.oLoadedState = $.extend( true, {}, state );
+
+ // Restore key features - todo - for 1.11 this needs to be done by
+ // subscribed events
+ if ( state.start !== undefined ) {
+ settings._iDisplayStart = state.start;
+ settings.iInitDisplayStart = state.start;
+ }
+ if ( state.length !== undefined ) {
+ settings._iDisplayLength = state.length;
+ }
+
+ // Order
+ if ( state.order !== undefined ) {
+ settings.aaSorting = [];
+ $.each( state.order, function ( i, col ) {
+ settings.aaSorting.push( col[0] >= columns.length ?
+ [ 0, col[1] ] :
+ col
+ );
+ } );
+ }
+
+ // Search
+ if ( state.search !== undefined ) {
+ $.extend( settings.oPreviousSearch, _fnSearchToHung( state.search ) );
+ }
+
+ // Columns
+ for ( i=0, ien=state.columns.length ; i= end )
+ {
+ start = end - len;
+ }
+
+ // Keep the start record on the current page
+ start -= (start % len);
+
+ if ( len === -1 || start < 0 )
+ {
+ start = 0;
+ }
+
+ settings._iDisplayStart = start;
+ }
+
+
+ function _fnRenderer( settings, type )
+ {
+ var renderer = settings.renderer;
+ var host = DataTable.ext.renderer[type];
+
+ if ( $.isPlainObject( renderer ) && renderer[type] ) {
+ // Specific renderer for this type. If available use it, otherwise use
+ // the default.
+ return host[renderer[type]] || host._;
+ }
+ else if ( typeof renderer === 'string' ) {
+ // Common renderer - if there is one available for this type use it,
+ // otherwise use the default
+ return host[renderer] || host._;
+ }
+
+ // Use the default
+ return host._;
+ }
+
+
+ /**
+ * Detect the data source being used for the table. Used to simplify the code
+ * a little (ajax) and to make it compress a little smaller.
+ *
+ * @param {object} settings dataTables settings object
+ * @returns {string} Data source
+ * @memberof DataTable#oApi
+ */
+ function _fnDataSource ( settings )
+ {
+ if ( settings.oFeatures.bServerSide ) {
+ return 'ssp';
+ }
+ else if ( settings.ajax || settings.sAjaxSource ) {
+ return 'ajax';
+ }
+ return 'dom';
+ }
+
+
+ DataTable = function( options )
+ {
+ /**
+ * Perform a jQuery selector action on the table's TR elements (from the tbody) and
+ * return the resulting jQuery object.
+ * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
+ * @param {object} [oOpts] Optional parameters for modifying the rows to be included
+ * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter
+ * criterion ("applied") or all TR elements (i.e. no filter).
+ * @param {string} [oOpts.order=current] Order of the TR elements in the processed array.
+ * Can be either 'current', whereby the current sorting of the table is used, or
+ * 'original' whereby the original order the data was read into the table is used.
+ * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
+ * ("current") or not ("all"). If 'current' is given, then order is assumed to be
+ * 'current' and filter is 'applied', regardless of what they might be given as.
+ * @returns {object} jQuery object, filtered by the given selector.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Highlight every second row
+ * oTable.$('tr:odd').css('backgroundColor', 'blue');
+ * } );
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Filter to rows with 'Webkit' in them, add a background colour and then
+ * // remove the filter, thus highlighting the 'Webkit' rows only.
+ * oTable.fnFilter('Webkit');
+ * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue');
+ * oTable.fnFilter('');
+ * } );
+ */
+ this.$ = function ( sSelector, oOpts )
+ {
+ return this.api(true).$( sSelector, oOpts );
+ };
+
+
+ /**
+ * Almost identical to $ in operation, but in this case returns the data for the matched
+ * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes
+ * rather than any descendants, so the data can be obtained for the row/cell. If matching
+ * rows are found, the data returned is the original data array/object that was used to
+ * create the row (or a generated array if from a DOM source).
+ *
+ * This method is often useful in-combination with $ where both functions are given the
+ * same parameters and the array indexes will match identically.
+ * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
+ * @param {object} [oOpts] Optional parameters for modifying the rows to be included
+ * @param {string} [oOpts.filter=none] Select elements that meet the current filter
+ * criterion ("applied") or all elements (i.e. no filter).
+ * @param {string} [oOpts.order=current] Order of the data in the processed array.
+ * Can be either 'current', whereby the current sorting of the table is used, or
+ * 'original' whereby the original order the data was read into the table is used.
+ * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
+ * ("current") or not ("all"). If 'current' is given, then order is assumed to be
+ * 'current' and filter is 'applied', regardless of what they might be given as.
+ * @returns {array} Data for the matched elements. If any elements, as a result of the
+ * selector, were not TR, TD or TH elements in the DataTable, they will have a null
+ * entry in the array.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Get the data from the first row in the table
+ * var data = oTable._('tr:first');
+ *
+ * // Do something useful with the data
+ * alert( "First cell is: "+data[0] );
+ * } );
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Filter to 'Webkit' and get all data for
+ * oTable.fnFilter('Webkit');
+ * var data = oTable._('tr', {"search": "applied"});
+ *
+ * // Do something with the data
+ * alert( data.length+" rows matched the search" );
+ * } );
+ */
+ this._ = function ( sSelector, oOpts )
+ {
+ return this.api(true).rows( sSelector, oOpts ).data();
+ };
+
+
+ /**
+ * Create a DataTables Api instance, with the currently selected tables for
+ * the Api's context.
+ * @param {boolean} [traditional=false] Set the API instance's context to be
+ * only the table referred to by the `DataTable.ext.iApiIndex` option, as was
+ * used in the API presented by DataTables 1.9- (i.e. the traditional mode),
+ * or if all tables captured in the jQuery object should be used.
+ * @return {DataTables.Api}
+ */
+ this.api = function ( traditional )
+ {
+ return traditional ?
+ new _Api(
+ _fnSettingsFromNode( this[ _ext.iApiIndex ] )
+ ) :
+ new _Api( this );
+ };
+
+
+ /**
+ * Add a single new row or multiple rows of data to the table. Please note
+ * that this is suitable for client-side processing only - if you are using
+ * server-side processing (i.e. "bServerSide": true), then to add data, you
+ * must add it to the data source, i.e. the server-side, through an Ajax call.
+ * @param {array|object} data The data to be added to the table. This can be:
+ *
+ * 1D array of data - add a single row with the data provided
+ * 2D array of arrays - add multiple rows in a single call
+ * object - data object when using mData
+ * array of objects - multiple data objects when using mData
+ *
+ * @param {bool} [redraw=true] redraw the table or not
+ * @returns {array} An array of integers, representing the list of indexes in
+ * aoData ({@link DataTable.models.oSettings}) that have been added to
+ * the table.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * // Global var for counter
+ * var giCount = 2;
+ *
+ * $(document).ready(function() {
+ * $('#example').dataTable();
+ * } );
+ *
+ * function fnClickAddRow() {
+ * $('#example').dataTable().fnAddData( [
+ * giCount+".1",
+ * giCount+".2",
+ * giCount+".3",
+ * giCount+".4" ]
+ * );
+ *
+ * giCount++;
+ * }
+ */
+ this.fnAddData = function( data, redraw )
+ {
+ var api = this.api( true );
+
+ /* Check if we want to add multiple rows or not */
+ var rows = $.isArray(data) && ( $.isArray(data[0]) || $.isPlainObject(data[0]) ) ?
+ api.rows.add( data ) :
+ api.row.add( data );
+
+ if ( redraw === undefined || redraw ) {
+ api.draw();
+ }
+
+ return rows.flatten().toArray();
+ };
+
+
+ /**
+ * This function will make DataTables recalculate the column sizes, based on the data
+ * contained in the table and the sizes applied to the columns (in the DOM, CSS or
+ * through the sWidth parameter). This can be useful when the width of the table's
+ * parent element changes (for example a window resize).
+ * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable( {
+ * "sScrollY": "200px",
+ * "bPaginate": false
+ * } );
+ *
+ * $(window).bind('resize', function () {
+ * oTable.fnAdjustColumnSizing();
+ * } );
+ * } );
+ */
+ this.fnAdjustColumnSizing = function ( bRedraw )
+ {
+ var api = this.api( true ).columns.adjust();
+ var settings = api.settings()[0];
+ var scroll = settings.oScroll;
+
+ if ( bRedraw === undefined || bRedraw ) {
+ api.draw( false );
+ }
+ else if ( scroll.sX !== "" || scroll.sY !== "" ) {
+ /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */
+ _fnScrollDraw( settings );
+ }
+ };
+
+
+ /**
+ * Quickly and simply clear a table
+ * @param {bool} [bRedraw=true] redraw the table or not
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...)
+ * oTable.fnClearTable();
+ * } );
+ */
+ this.fnClearTable = function( bRedraw )
+ {
+ var api = this.api( true ).clear();
+
+ if ( bRedraw === undefined || bRedraw ) {
+ api.draw();
+ }
+ };
+
+
+ /**
+ * The exact opposite of 'opening' a row, this function will close any rows which
+ * are currently 'open'.
+ * @param {node} nTr the table row to 'close'
+ * @returns {int} 0 on success, or 1 if failed (can't find the row)
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable;
+ *
+ * // 'open' an information row when a row is clicked on
+ * $('#example tbody tr').click( function () {
+ * if ( oTable.fnIsOpen(this) ) {
+ * oTable.fnClose( this );
+ * } else {
+ * oTable.fnOpen( this, "Temporary row opened", "info_row" );
+ * }
+ * } );
+ *
+ * oTable = $('#example').dataTable();
+ * } );
+ */
+ this.fnClose = function( nTr )
+ {
+ this.api( true ).row( nTr ).child.hide();
+ };
+
+
+ /**
+ * Remove a row for the table
+ * @param {mixed} target The index of the row from aoData to be deleted, or
+ * the TR element you want to delete
+ * @param {function|null} [callBack] Callback function
+ * @param {bool} [redraw=true] Redraw the table or not
+ * @returns {array} The row that was deleted
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Immediately remove the first row
+ * oTable.fnDeleteRow( 0 );
+ * } );
+ */
+ this.fnDeleteRow = function( target, callback, redraw )
+ {
+ var api = this.api( true );
+ var rows = api.rows( target );
+ var settings = rows.settings()[0];
+ var data = settings.aoData[ rows[0][0] ];
+
+ rows.remove();
+
+ if ( callback ) {
+ callback.call( this, settings, data );
+ }
+
+ if ( redraw === undefined || redraw ) {
+ api.draw();
+ }
+
+ return data;
+ };
+
+
+ /**
+ * Restore the table to it's original state in the DOM by removing all of DataTables
+ * enhancements, alterations to the DOM structure of the table and event listeners.
+ * @param {boolean} [remove=false] Completely remove the table from the DOM
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * // This example is fairly pointless in reality, but shows how fnDestroy can be used
+ * var oTable = $('#example').dataTable();
+ * oTable.fnDestroy();
+ * } );
+ */
+ this.fnDestroy = function ( remove )
+ {
+ this.api( true ).destroy( remove );
+ };
+
+
+ /**
+ * Redraw the table
+ * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Re-draw the table - you wouldn't want to do it here, but it's an example :-)
+ * oTable.fnDraw();
+ * } );
+ */
+ this.fnDraw = function( complete )
+ {
+ // Note that this isn't an exact match to the old call to _fnDraw - it takes
+ // into account the new data, but can hold position.
+ this.api( true ).draw( complete );
+ };
+
+
+ /**
+ * Filter the input based on data
+ * @param {string} sInput String to filter the table on
+ * @param {int|null} [iColumn] Column to limit filtering to
+ * @param {bool} [bRegex=false] Treat as regular expression or not
+ * @param {bool} [bSmart=true] Perform smart filtering or not
+ * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es)
+ * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false)
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Sometime later - filter...
+ * oTable.fnFilter( 'test string' );
+ * } );
+ */
+ this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive )
+ {
+ var api = this.api( true );
+
+ if ( iColumn === null || iColumn === undefined ) {
+ api.search( sInput, bRegex, bSmart, bCaseInsensitive );
+ }
+ else {
+ api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive );
+ }
+
+ api.draw();
+ };
+
+
+ /**
+ * Get the data for the whole table, an individual row or an individual cell based on the
+ * provided parameters.
+ * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as
+ * a TR node then the data source for the whole row will be returned. If given as a
+ * TD/TH cell node then iCol will be automatically calculated and the data for the
+ * cell returned. If given as an integer, then this is treated as the aoData internal
+ * data index for the row (see fnGetPosition) and the data for that row used.
+ * @param {int} [col] Optional column index that you want the data of.
+ * @returns {array|object|string} If mRow is undefined, then the data for all rows is
+ * returned. If mRow is defined, just data for that row, and is iCol is
+ * defined, only data for the designated cell is returned.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * // Row data
+ * $(document).ready(function() {
+ * oTable = $('#example').dataTable();
+ *
+ * oTable.$('tr').click( function () {
+ * var data = oTable.fnGetData( this );
+ * // ... do something with the array / object of data for the row
+ * } );
+ * } );
+ *
+ * @example
+ * // Individual cell data
+ * $(document).ready(function() {
+ * oTable = $('#example').dataTable();
+ *
+ * oTable.$('td').click( function () {
+ * var sData = oTable.fnGetData( this );
+ * alert( 'The cell clicked on had the value of '+sData );
+ * } );
+ * } );
+ */
+ this.fnGetData = function( src, col )
+ {
+ var api = this.api( true );
+
+ if ( src !== undefined ) {
+ var type = src.nodeName ? src.nodeName.toLowerCase() : '';
+
+ return col !== undefined || type == 'td' || type == 'th' ?
+ api.cell( src, col ).data() :
+ api.row( src ).data() || null;
+ }
+
+ return api.data().toArray();
+ };
+
+
+ /**
+ * Get an array of the TR nodes that are used in the table's body. Note that you will
+ * typically want to use the '$' API method in preference to this as it is more
+ * flexible.
+ * @param {int} [iRow] Optional row index for the TR element you want
+ * @returns {array|node} If iRow is undefined, returns an array of all TR elements
+ * in the table's body, or iRow is defined, just the TR element requested.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Get the nodes from the table
+ * var nNodes = oTable.fnGetNodes( );
+ * } );
+ */
+ this.fnGetNodes = function( iRow )
+ {
+ var api = this.api( true );
+
+ return iRow !== undefined ?
+ api.row( iRow ).node() :
+ api.rows().nodes().flatten().toArray();
+ };
+
+
+ /**
+ * Get the array indexes of a particular cell from it's DOM element
+ * and column index including hidden columns
+ * @param {node} node this can either be a TR, TD or TH in the table's body
+ * @returns {int} If nNode is given as a TR, then a single index is returned, or
+ * if given as a cell, an array of [row index, column index (visible),
+ * column index (all)] is given.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * $('#example tbody td').click( function () {
+ * // Get the position of the current data from the node
+ * var aPos = oTable.fnGetPosition( this );
+ *
+ * // Get the data array for this row
+ * var aData = oTable.fnGetData( aPos[0] );
+ *
+ * // Update the data array and return the value
+ * aData[ aPos[1] ] = 'clicked';
+ * this.innerHTML = 'clicked';
+ * } );
+ *
+ * // Init DataTables
+ * oTable = $('#example').dataTable();
+ * } );
+ */
+ this.fnGetPosition = function( node )
+ {
+ var api = this.api( true );
+ var nodeName = node.nodeName.toUpperCase();
+
+ if ( nodeName == 'TR' ) {
+ return api.row( node ).index();
+ }
+ else if ( nodeName == 'TD' || nodeName == 'TH' ) {
+ var cell = api.cell( node ).index();
+
+ return [
+ cell.row,
+ cell.columnVisible,
+ cell.column
+ ];
+ }
+ return null;
+ };
+
+
+ /**
+ * Check to see if a row is 'open' or not.
+ * @param {node} nTr the table row to check
+ * @returns {boolean} true if the row is currently open, false otherwise
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable;
+ *
+ * // 'open' an information row when a row is clicked on
+ * $('#example tbody tr').click( function () {
+ * if ( oTable.fnIsOpen(this) ) {
+ * oTable.fnClose( this );
+ * } else {
+ * oTable.fnOpen( this, "Temporary row opened", "info_row" );
+ * }
+ * } );
+ *
+ * oTable = $('#example').dataTable();
+ * } );
+ */
+ this.fnIsOpen = function( nTr )
+ {
+ return this.api( true ).row( nTr ).child.isShown();
+ };
+
+
+ /**
+ * This function will place a new row directly after a row which is currently
+ * on display on the page, with the HTML contents that is passed into the
+ * function. This can be used, for example, to ask for confirmation that a
+ * particular record should be deleted.
+ * @param {node} nTr The table row to 'open'
+ * @param {string|node|jQuery} mHtml The HTML to put into the row
+ * @param {string} sClass Class to give the new TD cell
+ * @returns {node} The row opened. Note that if the table row passed in as the
+ * first parameter, is not found in the table, this method will silently
+ * return.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable;
+ *
+ * // 'open' an information row when a row is clicked on
+ * $('#example tbody tr').click( function () {
+ * if ( oTable.fnIsOpen(this) ) {
+ * oTable.fnClose( this );
+ * } else {
+ * oTable.fnOpen( this, "Temporary row opened", "info_row" );
+ * }
+ * } );
+ *
+ * oTable = $('#example').dataTable();
+ * } );
+ */
+ this.fnOpen = function( nTr, mHtml, sClass )
+ {
+ return this.api( true )
+ .row( nTr )
+ .child( mHtml, sClass )
+ .show()
+ .child()[0];
+ };
+
+
+ /**
+ * Change the pagination - provides the internal logic for pagination in a simple API
+ * function. With this function you can have a DataTables table go to the next,
+ * previous, first or last pages.
+ * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last"
+ * or page number to jump to (integer), note that page 0 is the first page.
+ * @param {bool} [bRedraw=true] Redraw the table or not
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ * oTable.fnPageChange( 'next' );
+ * } );
+ */
+ this.fnPageChange = function ( mAction, bRedraw )
+ {
+ var api = this.api( true ).page( mAction );
+
+ if ( bRedraw === undefined || bRedraw ) {
+ api.draw(false);
+ }
+ };
+
+
+ /**
+ * Show a particular column
+ * @param {int} iCol The column whose display should be changed
+ * @param {bool} bShow Show (true) or hide (false) the column
+ * @param {bool} [bRedraw=true] Redraw the table or not
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Hide the second column after initialisation
+ * oTable.fnSetColumnVis( 1, false );
+ * } );
+ */
+ this.fnSetColumnVis = function ( iCol, bShow, bRedraw )
+ {
+ var api = this.api( true ).column( iCol ).visible( bShow );
+
+ if ( bRedraw === undefined || bRedraw ) {
+ api.columns.adjust().draw();
+ }
+ };
+
+
+ /**
+ * Get the settings for a particular table for external manipulation
+ * @returns {object} DataTables settings object. See
+ * {@link DataTable.models.oSettings}
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ * var oSettings = oTable.fnSettings();
+ *
+ * // Show an example parameter from the settings
+ * alert( oSettings._iDisplayStart );
+ * } );
+ */
+ this.fnSettings = function()
+ {
+ return _fnSettingsFromNode( this[_ext.iApiIndex] );
+ };
+
+
+ /**
+ * Sort the table by a particular column
+ * @param {int} iCol the data index to sort on. Note that this will not match the
+ * 'display index' if you have hidden data entries
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Sort immediately with columns 0 and 1
+ * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] );
+ * } );
+ */
+ this.fnSort = function( aaSort )
+ {
+ this.api( true ).order( aaSort ).draw();
+ };
+
+
+ /**
+ * Attach a sort listener to an element for a given column
+ * @param {node} nNode the element to attach the sort listener to
+ * @param {int} iColumn the column that a click on this node will sort on
+ * @param {function} [fnCallback] callback function when sort is run
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Sort on column 1, when 'sorter' is clicked on
+ * oTable.fnSortListener( document.getElementById('sorter'), 1 );
+ * } );
+ */
+ this.fnSortListener = function( nNode, iColumn, fnCallback )
+ {
+ this.api( true ).order.listener( nNode, iColumn, fnCallback );
+ };
+
+
+ /**
+ * Update a table cell or row - this method will accept either a single value to
+ * update the cell with, an array of values with one element for each column or
+ * an object in the same format as the original data source. The function is
+ * self-referencing in order to make the multi column updates easier.
+ * @param {object|array|string} mData Data to update the cell/row with
+ * @param {node|int} mRow TR element you want to update or the aoData index
+ * @param {int} [iColumn] The column to update, give as null or undefined to
+ * update a whole row.
+ * @param {bool} [bRedraw=true] Redraw the table or not
+ * @param {bool} [bAction=true] Perform pre-draw actions or not
+ * @returns {int} 0 on success, 1 on error
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell
+ * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row
+ * } );
+ */
+ this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction )
+ {
+ var api = this.api( true );
+
+ if ( iColumn === undefined || iColumn === null ) {
+ api.row( mRow ).data( mData );
+ }
+ else {
+ api.cell( mRow, iColumn ).data( mData );
+ }
+
+ if ( bAction === undefined || bAction ) {
+ api.columns.adjust();
+ }
+
+ if ( bRedraw === undefined || bRedraw ) {
+ api.draw();
+ }
+ return 0;
+ };
+
+
+ /**
+ * Provide a common method for plug-ins to check the version of DataTables being used, in order
+ * to ensure compatibility.
+ * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the
+ * formats "X" and "X.Y" are also acceptable.
+ * @returns {boolean} true if this version of DataTables is greater or equal to the required
+ * version, or false if this version of DataTales is not suitable
+ * @method
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ * alert( oTable.fnVersionCheck( '1.9.0' ) );
+ * } );
+ */
+ this.fnVersionCheck = _ext.fnVersionCheck;
+
+
+ var _that = this;
+ var emptyInit = options === undefined;
+ var len = this.length;
+
+ if ( emptyInit ) {
+ options = {};
+ }
+
+ this.oApi = this.internal = _ext.internal;
+
+ // Extend with old style plug-in API methods
+ for ( var fn in DataTable.ext.internal ) {
+ if ( fn ) {
+ this[fn] = _fnExternApiFunc(fn);
+ }
+ }
+
+ this.each(function() {
+ // For each initialisation we want to give it a clean initialisation
+ // object that can be bashed around
+ var o = {};
+ var oInit = len > 1 ? // optimisation for single table case
+ _fnExtend( o, options, true ) :
+ options;
+
+ /*global oInit,_that,emptyInit*/
+ var i=0, iLen, j, jLen, k, kLen;
+ var sId = this.getAttribute( 'id' );
+ var bInitHandedOff = false;
+ var defaults = DataTable.defaults;
+ var $this = $(this);
+
+
+ /* Sanity check */
+ if ( this.nodeName.toLowerCase() != 'table' )
+ {
+ _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 );
+ return;
+ }
+
+ /* Backwards compatibility for the defaults */
+ _fnCompatOpts( defaults );
+ _fnCompatCols( defaults.column );
+
+ /* Convert the camel-case defaults to Hungarian */
+ _fnCamelToHungarian( defaults, defaults, true );
+ _fnCamelToHungarian( defaults.column, defaults.column, true );
+
+ /* Setting up the initialisation object */
+ _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ) );
+
+
+
+ /* Check to see if we are re-initialising a table */
+ var allSettings = DataTable.settings;
+ for ( i=0, iLen=allSettings.length ; i').appendTo(this);
+ }
+ oSettings.nTHead = thead[0];
+
+ var tbody = $this.children('tbody');
+ if ( tbody.length === 0 )
+ {
+ tbody = $(' ').appendTo(this);
+ }
+ oSettings.nTBody = tbody[0];
+
+ var tfoot = $this.children('tfoot');
+ if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") )
+ {
+ // If we are a scrolling table, and no footer has been given, then we need to create
+ // a tfoot element for the caption element to be appended to
+ tfoot = $(' ').appendTo(this);
+ }
+
+ if ( tfoot.length === 0 || tfoot.children().length === 0 ) {
+ $this.addClass( oClasses.sNoFooter );
+ }
+ else if ( tfoot.length > 0 ) {
+ oSettings.nTFoot = tfoot[0];
+ _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot );
+ }
+
+ /* Check if there is data passing into the constructor */
+ if ( oInit.aaData )
+ {
+ for ( i=0 ; i idx ?
+ new _Api( ctx[idx], this[idx] ) :
+ null;
+ },
+
+
+ filter: function ( fn )
+ {
+ var a = [];
+
+ if ( __arrayProto.filter ) {
+ a = __arrayProto.filter.call( this, fn, this );
+ }
+ else {
+ // Compatibility for browsers without EMCA-252-5 (JS 1.6)
+ for ( var i=0, ien=this.length ; i 0 ) {
+ return ctx[0].json;
+ }
+
+ // else return undefined;
+ } );
+
+
+ /**
+ * Get the data submitted in the last Ajax request
+ */
+ _api_register( 'ajax.params()', function () {
+ var ctx = this.context;
+
+ if ( ctx.length > 0 ) {
+ return ctx[0].oAjaxData;
+ }
+
+ // else return undefined;
+ } );
+
+
+ /**
+ * Reload tables from the Ajax data source. Note that this function will
+ * automatically re-draw the table when the remote data has been loaded.
+ *
+ * @param {boolean} [reset=true] Reset (default) or hold the current paging
+ * position. A full re-sort and re-filter is performed when this method is
+ * called, which is why the pagination reset is the default action.
+ * @returns {DataTables.Api} this
+ */
+ _api_register( 'ajax.reload()', function ( callback, resetPaging ) {
+ return this.iterator( 'table', function (settings) {
+ __reload( settings, resetPaging===false, callback );
+ } );
+ } );
+
+
+ /**
+ * Get the current Ajax URL. Note that this returns the URL from the first
+ * table in the current context.
+ *
+ * @return {string} Current Ajax source URL
+ *//**
+ * Set the Ajax URL. Note that this will set the URL for all tables in the
+ * current context.
+ *
+ * @param {string} url URL to set.
+ * @returns {DataTables.Api} this
+ */
+ _api_register( 'ajax.url()', function ( url ) {
+ var ctx = this.context;
+
+ if ( url === undefined ) {
+ // get
+ if ( ctx.length === 0 ) {
+ return undefined;
+ }
+ ctx = ctx[0];
+
+ return ctx.ajax ?
+ $.isPlainObject( ctx.ajax ) ?
+ ctx.ajax.url :
+ ctx.ajax :
+ ctx.sAjaxSource;
+ }
+
+ // set
+ return this.iterator( 'table', function ( settings ) {
+ if ( $.isPlainObject( settings.ajax ) ) {
+ settings.ajax.url = url;
+ }
+ else {
+ settings.ajax = url;
+ }
+ // No need to consider sAjaxSource here since DataTables gives priority
+ // to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any
+ // value of `sAjaxSource` redundant.
+ } );
+ } );
+
+
+ /**
+ * Load data from the newly set Ajax URL. Note that this method is only
+ * available when `ajax.url()` is used to set a URL. Additionally, this method
+ * has the same effect as calling `ajax.reload()` but is provided for
+ * convenience when setting a new URL. Like `ajax.reload()` it will
+ * automatically redraw the table once the remote data has been loaded.
+ *
+ * @returns {DataTables.Api} this
+ */
+ _api_register( 'ajax.url().load()', function ( callback, resetPaging ) {
+ // Same as a reload, but makes sense to present it for easy access after a
+ // url change
+ return this.iterator( 'table', function ( ctx ) {
+ __reload( ctx, resetPaging===false, callback );
+ } );
+ } );
+
+
+
+
+ var _selector_run = function ( type, selector, selectFn, settings, opts )
+ {
+ var
+ out = [], res,
+ a, i, ien, j, jen,
+ selectorType = typeof selector;
+
+ // Can't just check for isArray here, as an API or jQuery instance might be
+ // given with their array like look
+ if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) {
+ selector = [ selector ];
+ }
+
+ for ( i=0, ien=selector.length ; i 0 ) {
+ // Assign the first element to the first item in the instance
+ // and truncate the instance and context
+ inst[0] = inst[i];
+ inst[0].length = 1;
+ inst.length = 1;
+ inst.context = [ inst.context[i] ];
+
+ return inst;
+ }
+ }
+
+ // Not found - return an empty instance
+ inst.length = 0;
+ return inst;
+ };
+
+
+ var _selector_row_indexes = function ( settings, opts )
+ {
+ var
+ i, ien, tmp, a=[],
+ displayFiltered = settings.aiDisplay,
+ displayMaster = settings.aiDisplayMaster;
+
+ var
+ search = opts.search, // none, applied, removed
+ order = opts.order, // applied, current, index (original - compatibility with 1.9)
+ page = opts.page; // all, current
+
+ if ( _fnDataSource( settings ) == 'ssp' ) {
+ // In server-side processing mode, most options are irrelevant since
+ // rows not shown don't exist and the index order is the applied order
+ // Removed is a special case - for consistency just return an empty
+ // array
+ return search === 'removed' ?
+ [] :
+ _range( 0, displayMaster.length );
+ }
+ else if ( page == 'current' ) {
+ // Current page implies that order=current and fitler=applied, since it is
+ // fairly senseless otherwise, regardless of what order and search actually
+ // are
+ for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i= 0 && search == 'applied') )
+ {
+ a.push( i );
+ }
+ }
+ }
+ }
+
+ return a;
+ };
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Rows
+ *
+ * {} - no selector - use all available rows
+ * {integer} - row aoData index
+ * {node} - TR node
+ * {string} - jQuery selector to apply to the TR elements
+ * {array} - jQuery array of nodes, or simply an array of TR nodes
+ *
+ */
+
+
+ var __row_selector = function ( settings, selector, opts )
+ {
+ var run = function ( sel ) {
+ var selInt = _intVal( sel );
+ var i, ien;
+
+ // Short cut - selector is a number and no options provided (default is
+ // all records, so no need to check if the index is in there, since it
+ // must be - dev error if the index doesn't exist).
+ if ( selInt !== null && ! opts ) {
+ return [ selInt ];
+ }
+
+ var rows = _selector_row_indexes( settings, opts );
+
+ if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) {
+ // Selector - integer
+ return [ selInt ];
+ }
+ else if ( ! sel ) {
+ // Selector - none
+ return rows;
+ }
+
+ // Selector - function
+ if ( typeof sel === 'function' ) {
+ return $.map( rows, function (idx) {
+ var row = settings.aoData[ idx ];
+ return sel( idx, row._aData, row.nTr ) ? idx : null;
+ } );
+ }
+
+ // Get nodes in the order from the `rows` array with null values removed
+ var nodes = _removeEmpty(
+ _pluck_order( settings.aoData, rows, 'nTr' )
+ );
+
+ // Selector - node
+ if ( sel.nodeName ) {
+ if ( $.inArray( sel, nodes ) !== -1 ) {
+ return [ sel._DT_RowIndex ]; // sel is a TR node that is in the table
+ // and DataTables adds a prop for fast lookup
+ }
+ }
+
+ // ID selector. Want to always be able to select rows by id, regardless
+ // of if the tr element has been created or not, so can't rely upon
+ // jQuery here - hence a custom implementation. This does not match
+ // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything,
+ // but to select it using a CSS selector engine (like Sizzle or
+ // querySelect) it would need to need to be escaped for some characters.
+ // DataTables simplifies this for row selectors since you can select
+ // only a row. A # indicates an id any anything that follows is the id -
+ // unescaped.
+ if ( typeof sel === 'string' && sel.charAt(0) === '#' ) {
+ // get row index from id
+ var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ];
+ if ( rowObj !== undefined ) {
+ return [ rowObj.idx ];
+ }
+
+ // need to fall through to jQuery in case there is DOM id that
+ // matches
+ }
+
+ // Selector - jQuery selector string, array of nodes or jQuery object/
+ // As jQuery's .filter() allows jQuery objects to be passed in filter,
+ // it also allows arrays, so this will cope with all three options
+ return $(nodes)
+ .filter( sel )
+ .map( function () {
+ return this._DT_RowIndex;
+ } )
+ .toArray();
+ };
+
+ return _selector_run( 'row', selector, run, settings, opts );
+ };
+
+
+ _api_register( 'rows()', function ( selector, opts ) {
+ // argument shifting
+ if ( selector === undefined ) {
+ selector = '';
+ }
+ else if ( $.isPlainObject( selector ) ) {
+ opts = selector;
+ selector = '';
+ }
+
+ opts = _selector_opts( opts );
+
+ var inst = this.iterator( 'table', function ( settings ) {
+ return __row_selector( settings, selector, opts );
+ }, 1 );
+
+ // Want argument shifting here and in __row_selector?
+ inst.selector.rows = selector;
+ inst.selector.opts = opts;
+
+ return inst;
+ } );
+
+ _api_register( 'rows().nodes()', function () {
+ return this.iterator( 'row', function ( settings, row ) {
+ return settings.aoData[ row ].nTr || undefined;
+ }, 1 );
+ } );
+
+ _api_register( 'rows().data()', function () {
+ return this.iterator( true, 'rows', function ( settings, rows ) {
+ return _pluck_order( settings.aoData, rows, '_aData' );
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) {
+ return this.iterator( 'row', function ( settings, row ) {
+ var r = settings.aoData[ row ];
+ return type === 'search' ? r._aFilterData : r._aSortData;
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) {
+ return this.iterator( 'row', function ( settings, row ) {
+ _fnInvalidate( settings, row, src );
+ } );
+ } );
+
+ _api_registerPlural( 'rows().indexes()', 'row().index()', function () {
+ return this.iterator( 'row', function ( settings, row ) {
+ return row;
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) {
+ var a = [];
+ var context = this.context;
+
+ // `iterator` will drop undefined values, but in this case we want them
+ for ( var i=0, ien=context.length ; i ').addClass( k );
+ $('td', created)
+ .addClass( k )
+ .html( r )
+ [0].colSpan = _fnVisbleColumns( ctx );
+
+ rows.push( created[0] );
+ }
+ };
+
+ addRow( data, klass );
+
+ if ( row._details ) {
+ row._details.remove();
+ }
+
+ row._details = $(rows);
+
+ // If the children were already shown, that state should be retained
+ if ( row._detailsShow ) {
+ row._details.insertAfter( row.nTr );
+ }
+ };
+
+
+ var __details_remove = function ( api, idx )
+ {
+ var ctx = api.context;
+
+ if ( ctx.length ) {
+ var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ];
+
+ if ( row && row._details ) {
+ row._details.remove();
+
+ row._detailsShow = undefined;
+ row._details = undefined;
+ }
+ }
+ };
+
+
+ var __details_display = function ( api, show ) {
+ var ctx = api.context;
+
+ if ( ctx.length && api.length ) {
+ var row = ctx[0].aoData[ api[0] ];
+
+ if ( row._details ) {
+ row._detailsShow = show;
+
+ if ( show ) {
+ row._details.insertAfter( row.nTr );
+ }
+ else {
+ row._details.detach();
+ }
+
+ __details_events( ctx[0] );
+ }
+ }
+ };
+
+
+ var __details_events = function ( settings )
+ {
+ var api = new _Api( settings );
+ var namespace = '.dt.DT_details';
+ var drawEvent = 'draw'+namespace;
+ var colvisEvent = 'column-visibility'+namespace;
+ var destroyEvent = 'destroy'+namespace;
+ var data = settings.aoData;
+
+ api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent );
+
+ if ( _pluck( data, '_details' ).length > 0 ) {
+ // On each draw, insert the required elements into the document
+ api.on( drawEvent, function ( e, ctx ) {
+ if ( settings !== ctx ) {
+ return;
+ }
+
+ api.rows( {page:'current'} ).eq(0).each( function (idx) {
+ // Internal data grab
+ var row = data[ idx ];
+
+ if ( row._detailsShow ) {
+ row._details.insertAfter( row.nTr );
+ }
+ } );
+ } );
+
+ // Column visibility change - update the colspan
+ api.on( colvisEvent, function ( e, ctx, idx, vis ) {
+ if ( settings !== ctx ) {
+ return;
+ }
+
+ // Update the colspan for the details rows (note, only if it already has
+ // a colspan)
+ var row, visible = _fnVisbleColumns( ctx );
+
+ for ( var i=0, ien=data.length ; i=0 count from left, <0 count from right)
+ * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right)
+ * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right)
+ * "{string}:name" - column name
+ * "{string}" - jQuery selector on column header nodes
+ *
+ */
+
+ // can be an array of these items, comma separated list, or an array of comma
+ // separated lists
+
+ var __re_column_selector = /^(.+):(name|visIdx|visible)$/;
+
+
+ // r1 and r2 are redundant - but it means that the parameters match for the
+ // iterator callback in columns().data()
+ var __columnData = function ( settings, column, r1, r2, rows ) {
+ var a = [];
+ for ( var row=0, ien=rows.length ; row= 0 ?
+ selInt : // Count from left
+ columns.length + selInt // Count from right (+ because its a negative value)
+ ];
+ }
+
+ // Selector = function
+ if ( typeof s === 'function' ) {
+ var rows = _selector_row_indexes( settings, opts );
+
+ return $.map( columns, function (col, idx) {
+ return s(
+ idx,
+ __columnData( settings, idx, 0, 0, rows ),
+ nodes[ idx ]
+ ) ? idx : null;
+ } );
+ }
+
+ // jQuery or string selector
+ var match = typeof s === 'string' ?
+ s.match( __re_column_selector ) :
+ '';
+
+ if ( match ) {
+ switch( match[2] ) {
+ case 'visIdx':
+ case 'visible':
+ var idx = parseInt( match[1], 10 );
+ // Visible index given, convert to column index
+ if ( idx < 0 ) {
+ // Counting from the right
+ var visColumns = $.map( columns, function (col,i) {
+ return col.bVisible ? i : null;
+ } );
+ return [ visColumns[ visColumns.length + idx ] ];
+ }
+ // Counting from the left
+ return [ _fnVisibleToColumnIndex( settings, idx ) ];
+
+ case 'name':
+ // match by name. `names` is column index complete and in order
+ return $.map( names, function (name, i) {
+ return name === match[1] ? i : null;
+ } );
+ }
+ }
+ else {
+ // jQuery selector on the TH elements for the columns
+ return $( nodes )
+ .filter( s )
+ .map( function () {
+ return $.inArray( this, nodes ); // `nodes` is column index complete and in order
+ } )
+ .toArray();
+ }
+ };
+
+ return _selector_run( 'column', selector, run, settings, opts );
+ };
+
+
+ var __setColumnVis = function ( settings, column, vis, recalc ) {
+ var
+ cols = settings.aoColumns,
+ col = cols[ column ],
+ data = settings.aoData,
+ row, cells, i, ien, tr;
+
+ // Get
+ if ( vis === undefined ) {
+ return col.bVisible;
+ }
+
+ // Set
+ // No change
+ if ( col.bVisible === vis ) {
+ return;
+ }
+
+ if ( vis ) {
+ // Insert column
+ // Need to decide if we should use appendChild or insertBefore
+ var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 );
+
+ for ( i=0, ien=data.length ; i iThat;
+ }
+
+ return true;
+ };
+
+
+ /**
+ * Check if a `` node is a DataTable table already or not.
+ *
+ * @param {node|jquery|string} table Table node, jQuery object or jQuery
+ * selector for the table to test. Note that if more than more than one
+ * table is passed on, only the first will be checked
+ * @returns {boolean} true the table given is a DataTable, or false otherwise
+ * @static
+ * @dtopt API-Static
+ *
+ * @example
+ * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) {
+ * $('#example').dataTable();
+ * }
+ */
+ DataTable.isDataTable = DataTable.fnIsDataTable = function ( table )
+ {
+ var t = $(table).get(0);
+ var is = false;
+
+ $.each( DataTable.settings, function (i, o) {
+ var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null;
+ var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null;
+
+ if ( o.nTable === t || head === t || foot === t ) {
+ is = true;
+ }
+ } );
+
+ return is;
+ };
+
+
+ /**
+ * Get all DataTable tables that have been initialised - optionally you can
+ * select to get only currently visible tables.
+ *
+ * @param {boolean} [visible=false] Flag to indicate if you want all (default)
+ * or visible tables only.
+ * @returns {array} Array of `table` nodes (not DataTable instances) which are
+ * DataTables
+ * @static
+ * @dtopt API-Static
+ *
+ * @example
+ * $.each( $.fn.dataTable.tables(true), function () {
+ * $(table).DataTable().columns.adjust();
+ * } );
+ */
+ DataTable.tables = DataTable.fnTables = function ( visible )
+ {
+ var api = false;
+
+ if ( $.isPlainObject( visible ) ) {
+ api = visible.api;
+ visible = visible.visible;
+ }
+
+ var a = $.map( DataTable.settings, function (o) {
+ if ( !visible || (visible && $(o.nTable).is(':visible')) ) {
+ return o.nTable;
+ }
+ } );
+
+ return api ?
+ new _Api( a ) :
+ a;
+ };
+
+
+ /**
+ * DataTables utility methods
+ *
+ * This namespace provides helper methods that DataTables uses internally to
+ * create a DataTable, but which are not exclusively used only for DataTables.
+ * These methods can be used by extension authors to save the duplication of
+ * code.
+ *
+ * @namespace
+ */
+ DataTable.util = {
+ /**
+ * Throttle the calls to a function. Arguments and context are maintained
+ * for the throttled function.
+ *
+ * @param {function} fn Function to be called
+ * @param {integer} freq Call frequency in mS
+ * @return {function} Wrapped function
+ */
+ throttle: _fnThrottle,
+
+
+ /**
+ * Escape a string such that it can be used in a regular expression
+ *
+ * @param {string} sVal string to escape
+ * @returns {string} escaped string
+ */
+ escapeRegex: _fnEscapeRegex
+ };
+
+
+ /**
+ * Convert from camel case parameters to Hungarian notation. This is made public
+ * for the extensions to provide the same ability as DataTables core to accept
+ * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase
+ * parameters.
+ *
+ * @param {object} src The model object which holds all parameters that can be
+ * mapped.
+ * @param {object} user The object to convert from camel case to Hungarian.
+ * @param {boolean} force When set to `true`, properties which already have a
+ * Hungarian value in the `user` object will be overwritten. Otherwise they
+ * won't be.
+ */
+ DataTable.camelToHungarian = _fnCamelToHungarian;
+
+
+
+ /**
+ *
+ */
+ _api_register( '$()', function ( selector, opts ) {
+ var
+ rows = this.rows( opts ).nodes(), // Get all rows
+ jqRows = $(rows);
+
+ return $( [].concat(
+ jqRows.filter( selector ).toArray(),
+ jqRows.find( selector ).toArray()
+ ) );
+ } );
+
+
+ // jQuery functions to operate on the tables
+ $.each( [ 'on', 'one', 'off' ], function (i, key) {
+ _api_register( key+'()', function ( /* event, handler */ ) {
+ var args = Array.prototype.slice.call(arguments);
+
+ // Add the `dt` namespace automatically if it isn't already present
+ if ( ! args[0].match(/\.dt\b/) ) {
+ args[0] += '.dt';
+ }
+
+ var inst = $( this.tables().nodes() );
+ inst[key].apply( inst, args );
+ return this;
+ } );
+ } );
+
+
+ _api_register( 'clear()', function () {
+ return this.iterator( 'table', function ( settings ) {
+ _fnClearTable( settings );
+ } );
+ } );
+
+
+ _api_register( 'settings()', function () {
+ return new _Api( this.context, this.context );
+ } );
+
+
+ _api_register( 'init()', function () {
+ var ctx = this.context;
+ return ctx.length ? ctx[0].oInit : null;
+ } );
+
+
+ _api_register( 'data()', function () {
+ return this.iterator( 'table', function ( settings ) {
+ return _pluck( settings.aoData, '_aData' );
+ } ).flatten();
+ } );
+
+
+ _api_register( 'destroy()', function ( remove ) {
+ remove = remove || false;
+
+ return this.iterator( 'table', function ( settings ) {
+ var orig = settings.nTableWrapper.parentNode;
+ var classes = settings.oClasses;
+ var table = settings.nTable;
+ var tbody = settings.nTBody;
+ var thead = settings.nTHead;
+ var tfoot = settings.nTFoot;
+ var jqTable = $(table);
+ var jqTbody = $(tbody);
+ var jqWrapper = $(settings.nTableWrapper);
+ var rows = $.map( settings.aoData, function (r) { return r.nTr; } );
+ var i, ien;
+
+ // Flag to note that the table is currently being destroyed - no action
+ // should be taken
+ settings.bDestroying = true;
+
+ // Fire off the destroy callbacks for plug-ins etc
+ _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] );
+
+ // If not being removed from the document, make all columns visible
+ if ( ! remove ) {
+ new _Api( settings ).columns().visible( true );
+ }
+
+ // Blitz all `DT` namespaced events (these are internal events, the
+ // lowercase, `dt` events are user subscribed and they are responsible
+ // for removing them
+ jqWrapper.unbind('.DT').find(':not(tbody *)').unbind('.DT');
+ $(window).unbind('.DT-'+settings.sInstance);
+
+ // When scrolling we had to break the table up - restore it
+ if ( table != thead.parentNode ) {
+ jqTable.children('thead').detach();
+ jqTable.append( thead );
+ }
+
+ if ( tfoot && table != tfoot.parentNode ) {
+ jqTable.children('tfoot').detach();
+ jqTable.append( tfoot );
+ }
+
+ settings.aaSorting = [];
+ settings.aaSortingFixed = [];
+ _fnSortingClasses( settings );
+
+ $( rows ).removeClass( settings.asStripeClasses.join(' ') );
+
+ $('th, td', thead).removeClass( classes.sSortable+' '+
+ classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone
+ );
+
+ if ( settings.bJUI ) {
+ $('th span.'+classes.sSortIcon+ ', td span.'+classes.sSortIcon, thead).detach();
+ $('th, td', thead).each( function () {
+ var wrapper = $('div.'+classes.sSortJUIWrapper, this);
+ $(this).append( wrapper.contents() );
+ wrapper.detach();
+ } );
+ }
+
+ // Add the TR elements back into the table in their original order
+ jqTbody.children().detach();
+ jqTbody.append( rows );
+
+ // Remove the DataTables generated nodes, events and classes
+ var removedMethod = remove ? 'remove' : 'detach';
+ jqTable[ removedMethod ]();
+ jqWrapper[ removedMethod ]();
+
+ // If we need to reattach the table to the document
+ if ( ! remove && orig ) {
+ // insertBefore acts like appendChild if !arg[1]
+ orig.insertBefore( table, settings.nTableReinsertBefore );
+
+ // Restore the width of the original table - was read from the style property,
+ // so we can restore directly to that
+ jqTable
+ .css( 'width', settings.sDestroyWidth )
+ .removeClass( classes.sTable );
+
+ // If the were originally stripe classes - then we add them back here.
+ // Note this is not fool proof (for example if not all rows had stripe
+ // classes - but it's a good effort without getting carried away
+ ien = settings.asDestroyStripes.length;
+
+ if ( ien ) {
+ jqTbody.children().each( function (i) {
+ $(this).addClass( settings.asDestroyStripes[i % ien] );
+ } );
+ }
+ }
+
+ /* Remove the settings object from the settings array */
+ var idx = $.inArray( settings, DataTable.settings );
+ if ( idx !== -1 ) {
+ DataTable.settings.splice( idx, 1 );
+ }
+ } );
+ } );
+
+
+ // Add the `every()` method for rows, columns and cells in a compact form
+ $.each( [ 'column', 'row', 'cell' ], function ( i, type ) {
+ _api_register( type+'s().every()', function ( fn ) {
+ return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) {
+ // Rows and columns:
+ // arg1 - index
+ // arg2 - table counter
+ // arg3 - loop counter
+ // arg4 - undefined
+ // Cells:
+ // arg1 - row index
+ // arg2 - column index
+ // arg3 - table counter
+ // arg4 - loop counter
+ fn.call(
+ new _Api( settings )[ type ]( arg1, type==='cell' ? arg2 : undefined ),
+ arg1, arg2, arg3, arg4
+ );
+ } );
+ } );
+ } );
+
+
+ // i18n method for extensions to be able to use the language object from the
+ // DataTable
+ _api_register( 'i18n()', function ( token, def, plural ) {
+ var ctx = this.context[0];
+ var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage );
+
+ if ( resolved === undefined ) {
+ resolved = def;
+ }
+
+ if ( plural !== undefined && $.isPlainObject( resolved ) ) {
+ resolved = resolved[ plural ] !== undefined ?
+ resolved[ plural ] :
+ resolved._;
+ }
+
+ return resolved.replace( '%d', plural ); // nb: plural might be undefined,
+ } );
+
+ /**
+ * Version string for plug-ins to check compatibility. Allowed format is
+ * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used
+ * only for non-release builds. See http://semver.org/ for more information.
+ * @member
+ * @type string
+ * @default Version number
+ */
+ DataTable.version = "1.10.9";
+
+ /**
+ * Private data store, containing all of the settings objects that are
+ * created for the tables on a given page.
+ *
+ * Note that the `DataTable.settings` object is aliased to
+ * `jQuery.fn.dataTableExt` through which it may be accessed and
+ * manipulated, or `jQuery.fn.dataTable.settings`.
+ * @member
+ * @type array
+ * @default []
+ * @private
+ */
+ DataTable.settings = [];
+
+ /**
+ * Object models container, for the various models that DataTables has
+ * available to it. These models define the objects that are used to hold
+ * the active state and configuration of the table.
+ * @namespace
+ */
+ DataTable.models = {};
+
+
+
+ /**
+ * Template object for the way in which DataTables holds information about
+ * search information for the global filter and individual column filters.
+ * @namespace
+ */
+ DataTable.models.oSearch = {
+ /**
+ * Flag to indicate if the filtering should be case insensitive or not
+ * @type boolean
+ * @default true
+ */
+ "bCaseInsensitive": true,
+
+ /**
+ * Applied search term
+ * @type string
+ * @default Empty string
+ */
+ "sSearch": "",
+
+ /**
+ * Flag to indicate if the search term should be interpreted as a
+ * regular expression (true) or not (false) and therefore and special
+ * regex characters escaped.
+ * @type boolean
+ * @default false
+ */
+ "bRegex": false,
+
+ /**
+ * Flag to indicate if DataTables is to use its smart filtering or not.
+ * @type boolean
+ * @default true
+ */
+ "bSmart": true
+ };
+
+
+
+
+ /**
+ * Template object for the way in which DataTables holds information about
+ * each individual row. This is the object format used for the settings
+ * aoData array.
+ * @namespace
+ */
+ DataTable.models.oRow = {
+ /**
+ * TR element for the row
+ * @type node
+ * @default null
+ */
+ "nTr": null,
+
+ /**
+ * Array of TD elements for each row. This is null until the row has been
+ * created.
+ * @type array nodes
+ * @default []
+ */
+ "anCells": null,
+
+ /**
+ * Data object from the original data source for the row. This is either
+ * an array if using the traditional form of DataTables, or an object if
+ * using mData options. The exact type will depend on the passed in
+ * data from the data source, or will be an array if using DOM a data
+ * source.
+ * @type array|object
+ * @default []
+ */
+ "_aData": [],
+
+ /**
+ * Sorting data cache - this array is ostensibly the same length as the
+ * number of columns (although each index is generated only as it is
+ * needed), and holds the data that is used for sorting each column in the
+ * row. We do this cache generation at the start of the sort in order that
+ * the formatting of the sort data need be done only once for each cell
+ * per sort. This array should not be read from or written to by anything
+ * other than the master sorting methods.
+ * @type array
+ * @default null
+ * @private
+ */
+ "_aSortData": null,
+
+ /**
+ * Per cell filtering data cache. As per the sort data cache, used to
+ * increase the performance of the filtering in DataTables
+ * @type array
+ * @default null
+ * @private
+ */
+ "_aFilterData": null,
+
+ /**
+ * Filtering data cache. This is the same as the cell filtering cache, but
+ * in this case a string rather than an array. This is easily computed with
+ * a join on `_aFilterData`, but is provided as a cache so the join isn't
+ * needed on every search (memory traded for performance)
+ * @type array
+ * @default null
+ * @private
+ */
+ "_sFilterRow": null,
+
+ /**
+ * Cache of the class name that DataTables has applied to the row, so we
+ * can quickly look at this variable rather than needing to do a DOM check
+ * on className for the nTr property.
+ * @type string
+ * @default Empty string
+ * @private
+ */
+ "_sRowStripe": "",
+
+ /**
+ * Denote if the original data source was from the DOM, or the data source
+ * object. This is used for invalidating data, so DataTables can
+ * automatically read data from the original source, unless uninstructed
+ * otherwise.
+ * @type string
+ * @default null
+ * @private
+ */
+ "src": null,
+
+ /**
+ * Index in the aoData array. This saves an indexOf lookup when we have the
+ * object, but want to know the index
+ * @type integer
+ * @default -1
+ * @private
+ */
+ "idx": -1
+ };
+
+
+ /**
+ * Template object for the column information object in DataTables. This object
+ * is held in the settings aoColumns array and contains all the information that
+ * DataTables needs about each individual column.
+ *
+ * Note that this object is related to {@link DataTable.defaults.column}
+ * but this one is the internal data store for DataTables's cache of columns.
+ * It should NOT be manipulated outside of DataTables. Any configuration should
+ * be done through the initialisation options.
+ * @namespace
+ */
+ DataTable.models.oColumn = {
+ /**
+ * Column index. This could be worked out on-the-fly with $.inArray, but it
+ * is faster to just hold it as a variable
+ * @type integer
+ * @default null
+ */
+ "idx": null,
+
+ /**
+ * A list of the columns that sorting should occur on when this column
+ * is sorted. That this property is an array allows multi-column sorting
+ * to be defined for a column (for example first name / last name columns
+ * would benefit from this). The values are integers pointing to the
+ * columns to be sorted on (typically it will be a single integer pointing
+ * at itself, but that doesn't need to be the case).
+ * @type array
+ */
+ "aDataSort": null,
+
+ /**
+ * Define the sorting directions that are applied to the column, in sequence
+ * as the column is repeatedly sorted upon - i.e. the first value is used
+ * as the sorting direction when the column if first sorted (clicked on).
+ * Sort it again (click again) and it will move on to the next index.
+ * Repeat until loop.
+ * @type array
+ */
+ "asSorting": null,
+
+ /**
+ * Flag to indicate if the column is searchable, and thus should be included
+ * in the filtering or not.
+ * @type boolean
+ */
+ "bSearchable": null,
+
+ /**
+ * Flag to indicate if the column is sortable or not.
+ * @type boolean
+ */
+ "bSortable": null,
+
+ /**
+ * Flag to indicate if the column is currently visible in the table or not
+ * @type boolean
+ */
+ "bVisible": null,
+
+ /**
+ * Store for manual type assignment using the `column.type` option. This
+ * is held in store so we can manipulate the column's `sType` property.
+ * @type string
+ * @default null
+ * @private
+ */
+ "_sManualType": null,
+
+ /**
+ * Flag to indicate if HTML5 data attributes should be used as the data
+ * source for filtering or sorting. True is either are.
+ * @type boolean
+ * @default false
+ * @private
+ */
+ "_bAttrSrc": false,
+
+ /**
+ * Developer definable function that is called whenever a cell is created (Ajax source,
+ * etc) or processed for input (DOM source). This can be used as a compliment to mRender
+ * allowing you to modify the DOM element (add background colour for example) when the
+ * element is available.
+ * @type function
+ * @param {element} nTd The TD node that has been created
+ * @param {*} sData The Data for the cell
+ * @param {array|object} oData The data for the whole row
+ * @param {int} iRow The row index for the aoData data store
+ * @default null
+ */
+ "fnCreatedCell": null,
+
+ /**
+ * Function to get data from a cell in a column. You should never
+ * access data directly through _aData internally in DataTables - always use
+ * the method attached to this property. It allows mData to function as
+ * required. This function is automatically assigned by the column
+ * initialisation method
+ * @type function
+ * @param {array|object} oData The data array/object for the array
+ * (i.e. aoData[]._aData)
+ * @param {string} sSpecific The specific data type you want to get -
+ * 'display', 'type' 'filter' 'sort'
+ * @returns {*} The data for the cell from the given row's data
+ * @default null
+ */
+ "fnGetData": null,
+
+ /**
+ * Function to set data for a cell in the column. You should never
+ * set the data directly to _aData internally in DataTables - always use
+ * this method. It allows mData to function as required. This function
+ * is automatically assigned by the column initialisation method
+ * @type function
+ * @param {array|object} oData The data array/object for the array
+ * (i.e. aoData[]._aData)
+ * @param {*} sValue Value to set
+ * @default null
+ */
+ "fnSetData": null,
+
+ /**
+ * Property to read the value for the cells in the column from the data
+ * source array / object. If null, then the default content is used, if a
+ * function is given then the return from the function is used.
+ * @type function|int|string|null
+ * @default null
+ */
+ "mData": null,
+
+ /**
+ * Partner property to mData which is used (only when defined) to get
+ * the data - i.e. it is basically the same as mData, but without the
+ * 'set' option, and also the data fed to it is the result from mData.
+ * This is the rendering method to match the data method of mData.
+ * @type function|int|string|null
+ * @default null
+ */
+ "mRender": null,
+
+ /**
+ * Unique header TH/TD element for this column - this is what the sorting
+ * listener is attached to (if sorting is enabled.)
+ * @type node
+ * @default null
+ */
+ "nTh": null,
+
+ /**
+ * Unique footer TH/TD element for this column (if there is one). Not used
+ * in DataTables as such, but can be used for plug-ins to reference the
+ * footer for each column.
+ * @type node
+ * @default null
+ */
+ "nTf": null,
+
+ /**
+ * The class to apply to all TD elements in the table's TBODY for the column
+ * @type string
+ * @default null
+ */
+ "sClass": null,
+
+ /**
+ * When DataTables calculates the column widths to assign to each column,
+ * it finds the longest string in each column and then constructs a
+ * temporary table and reads the widths from that. The problem with this
+ * is that "mmm" is much wider then "iiii", but the latter is a longer
+ * string - thus the calculation can go wrong (doing it properly and putting
+ * it into an DOM object and measuring that is horribly(!) slow). Thus as
+ * a "work around" we provide this option. It will append its value to the
+ * text that is found to be the longest string for the column - i.e. padding.
+ * @type string
+ */
+ "sContentPadding": null,
+
+ /**
+ * Allows a default value to be given for a column's data, and will be used
+ * whenever a null data source is encountered (this can be because mData
+ * is set to null, or because the data source itself is null).
+ * @type string
+ * @default null
+ */
+ "sDefaultContent": null,
+
+ /**
+ * Name for the column, allowing reference to the column by name as well as
+ * by index (needs a lookup to work by name).
+ * @type string
+ */
+ "sName": null,
+
+ /**
+ * Custom sorting data type - defines which of the available plug-ins in
+ * afnSortData the custom sorting will use - if any is defined.
+ * @type string
+ * @default std
+ */
+ "sSortDataType": 'std',
+
+ /**
+ * Class to be applied to the header element when sorting on this column
+ * @type string
+ * @default null
+ */
+ "sSortingClass": null,
+
+ /**
+ * Class to be applied to the header element when sorting on this column -
+ * when jQuery UI theming is used.
+ * @type string
+ * @default null
+ */
+ "sSortingClassJUI": null,
+
+ /**
+ * Title of the column - what is seen in the TH element (nTh).
+ * @type string
+ */
+ "sTitle": null,
+
+ /**
+ * Column sorting and filtering type
+ * @type string
+ * @default null
+ */
+ "sType": null,
+
+ /**
+ * Width of the column
+ * @type string
+ * @default null
+ */
+ "sWidth": null,
+
+ /**
+ * Width of the column when it was first "encountered"
+ * @type string
+ * @default null
+ */
+ "sWidthOrig": null
+ };
+
+
+ /*
+ * Developer note: The properties of the object below are given in Hungarian
+ * notation, that was used as the interface for DataTables prior to v1.10, however
+ * from v1.10 onwards the primary interface is camel case. In order to avoid
+ * breaking backwards compatibility utterly with this change, the Hungarian
+ * version is still, internally the primary interface, but is is not documented
+ * - hence the @name tags in each doc comment. This allows a Javascript function
+ * to create a map from Hungarian notation to camel case (going the other direction
+ * would require each property to be listed, which would at around 3K to the size
+ * of DataTables, while this method is about a 0.5K hit.
+ *
+ * Ultimately this does pave the way for Hungarian notation to be dropped
+ * completely, but that is a massive amount of work and will break current
+ * installs (therefore is on-hold until v2).
+ */
+
+ /**
+ * Initialisation options that can be given to DataTables at initialisation
+ * time.
+ * @namespace
+ */
+ DataTable.defaults = {
+ /**
+ * An array of data to use for the table, passed in at initialisation which
+ * will be used in preference to any data which is already in the DOM. This is
+ * particularly useful for constructing tables purely in Javascript, for
+ * example with a custom Ajax call.
+ * @type array
+ * @default null
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.data
+ *
+ * @example
+ * // Using a 2D array data source
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "data": [
+ * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'],
+ * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'],
+ * ],
+ * "columns": [
+ * { "title": "Engine" },
+ * { "title": "Browser" },
+ * { "title": "Platform" },
+ * { "title": "Version" },
+ * { "title": "Grade" }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using an array of objects as a data source (`data`)
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "data": [
+ * {
+ * "engine": "Trident",
+ * "browser": "Internet Explorer 4.0",
+ * "platform": "Win 95+",
+ * "version": 4,
+ * "grade": "X"
+ * },
+ * {
+ * "engine": "Trident",
+ * "browser": "Internet Explorer 5.0",
+ * "platform": "Win 95+",
+ * "version": 5,
+ * "grade": "C"
+ * }
+ * ],
+ * "columns": [
+ * { "title": "Engine", "data": "engine" },
+ * { "title": "Browser", "data": "browser" },
+ * { "title": "Platform", "data": "platform" },
+ * { "title": "Version", "data": "version" },
+ * { "title": "Grade", "data": "grade" }
+ * ]
+ * } );
+ * } );
+ */
+ "aaData": null,
+
+
+ /**
+ * If ordering is enabled, then DataTables will perform a first pass sort on
+ * initialisation. You can define which column(s) the sort is performed
+ * upon, and the sorting direction, with this variable. The `sorting` array
+ * should contain an array for each column to be sorted initially containing
+ * the column's index and a direction string ('asc' or 'desc').
+ * @type array
+ * @default [[0,'asc']]
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.order
+ *
+ * @example
+ * // Sort by 3rd column first, and then 4th column
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "order": [[2,'asc'], [3,'desc']]
+ * } );
+ * } );
+ *
+ * // No initial sorting
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "order": []
+ * } );
+ * } );
+ */
+ "aaSorting": [[0,'asc']],
+
+
+ /**
+ * This parameter is basically identical to the `sorting` parameter, but
+ * cannot be overridden by user interaction with the table. What this means
+ * is that you could have a column (visible or hidden) which the sorting
+ * will always be forced on first - any sorting after that (from the user)
+ * will then be performed as required. This can be useful for grouping rows
+ * together.
+ * @type array
+ * @default null
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.orderFixed
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "orderFixed": [[0,'asc']]
+ * } );
+ * } )
+ */
+ "aaSortingFixed": [],
+
+
+ /**
+ * DataTables can be instructed to load data to display in the table from a
+ * Ajax source. This option defines how that Ajax call is made and where to.
+ *
+ * The `ajax` property has three different modes of operation, depending on
+ * how it is defined. These are:
+ *
+ * * `string` - Set the URL from where the data should be loaded from.
+ * * `object` - Define properties for `jQuery.ajax`.
+ * * `function` - Custom data get function
+ *
+ * `string`
+ * --------
+ *
+ * As a string, the `ajax` property simply defines the URL from which
+ * DataTables will load data.
+ *
+ * `object`
+ * --------
+ *
+ * As an object, the parameters in the object are passed to
+ * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control
+ * of the Ajax request. DataTables has a number of default parameters which
+ * you can override using this option. Please refer to the jQuery
+ * documentation for a full description of the options available, although
+ * the following parameters provide additional options in DataTables or
+ * require special consideration:
+ *
+ * * `data` - As with jQuery, `data` can be provided as an object, but it
+ * can also be used as a function to manipulate the data DataTables sends
+ * to the server. The function takes a single parameter, an object of
+ * parameters with the values that DataTables has readied for sending. An
+ * object may be returned which will be merged into the DataTables
+ * defaults, or you can add the items to the object that was passed in and
+ * not return anything from the function. This supersedes `fnServerParams`
+ * from DataTables 1.9-.
+ *
+ * * `dataSrc` - By default DataTables will look for the property `data` (or
+ * `aaData` for compatibility with DataTables 1.9-) when obtaining data
+ * from an Ajax source or for server-side processing - this parameter
+ * allows that property to be changed. You can use Javascript dotted
+ * object notation to get a data source for multiple levels of nesting, or
+ * it my be used as a function. As a function it takes a single parameter,
+ * the JSON returned from the server, which can be manipulated as
+ * required, with the returned value being that used by DataTables as the
+ * data source for the table. This supersedes `sAjaxDataProp` from
+ * DataTables 1.9-.
+ *
+ * * `success` - Should not be overridden it is used internally in
+ * DataTables. To manipulate / transform the data returned by the server
+ * use `ajax.dataSrc`, or use `ajax` as a function (see below).
+ *
+ * `function`
+ * ----------
+ *
+ * As a function, making the Ajax call is left up to yourself allowing
+ * complete control of the Ajax request. Indeed, if desired, a method other
+ * than Ajax could be used to obtain the required data, such as Web storage
+ * or an AIR database.
+ *
+ * The function is given four parameters and no return is required. The
+ * parameters are:
+ *
+ * 1. _object_ - Data to send to the server
+ * 2. _function_ - Callback function that must be executed when the required
+ * data has been obtained. That data should be passed into the callback
+ * as the only parameter
+ * 3. _object_ - DataTables settings object for the table
+ *
+ * Note that this supersedes `fnServerData` from DataTables 1.9-.
+ *
+ * @type string|object|function
+ * @default null
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.ajax
+ * @since 1.10.0
+ *
+ * @example
+ * // Get JSON data from a file via Ajax.
+ * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default).
+ * $('#example').dataTable( {
+ * "ajax": "data.json"
+ * } );
+ *
+ * @example
+ * // Get JSON data from a file via Ajax, using `dataSrc` to change
+ * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`)
+ * $('#example').dataTable( {
+ * "ajax": {
+ * "url": "data.json",
+ * "dataSrc": "tableData"
+ * }
+ * } );
+ *
+ * @example
+ * // Get JSON data from a file via Ajax, using `dataSrc` to read data
+ * // from a plain array rather than an array in an object
+ * $('#example').dataTable( {
+ * "ajax": {
+ * "url": "data.json",
+ * "dataSrc": ""
+ * }
+ * } );
+ *
+ * @example
+ * // Manipulate the data returned from the server - add a link to data
+ * // (note this can, should, be done using `render` for the column - this
+ * // is just a simple example of how the data can be manipulated).
+ * $('#example').dataTable( {
+ * "ajax": {
+ * "url": "data.json",
+ * "dataSrc": function ( json ) {
+ * for ( var i=0, ien=json.length ; i
+ * a string - class name will be matched on the TH for the column
+ * 0 or a positive integer - column index counting from the left
+ * a negative integer - column index counting from the right
+ * the string "_all" - all columns (i.e. assign a default)
+ *
+ * @member
+ *
+ * @name DataTable.defaults.columnDefs
+ */
+ "aoColumnDefs": null,
+
+
+ /**
+ * Basically the same as `search`, this parameter defines the individual column
+ * filtering state at initialisation time. The array must be of the same size
+ * as the number of columns, and each element be an object with the parameters
+ * `search` and `escapeRegex` (the latter is optional). 'null' is also
+ * accepted and the default will be used.
+ * @type array
+ * @default []
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.searchCols
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "searchCols": [
+ * null,
+ * { "search": "My filter" },
+ * null,
+ * { "search": "^[0-9]", "escapeRegex": false }
+ * ]
+ * } );
+ * } )
+ */
+ "aoSearchCols": [],
+
+
+ /**
+ * An array of CSS classes that should be applied to displayed rows. This
+ * array may be of any length, and DataTables will apply each class
+ * sequentially, looping when required.
+ * @type array
+ * @default null Will take the values determined by the `oClasses.stripe*`
+ * options
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.stripeClasses
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ]
+ * } );
+ * } )
+ */
+ "asStripeClasses": null,
+
+
+ /**
+ * Enable or disable automatic column width calculation. This can be disabled
+ * as an optimisation (it takes some time to calculate the widths) if the
+ * tables widths are passed in using `columns`.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.autoWidth
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "autoWidth": false
+ * } );
+ * } );
+ */
+ "bAutoWidth": true,
+
+
+ /**
+ * Deferred rendering can provide DataTables with a huge speed boost when you
+ * are using an Ajax or JS data source for the table. This option, when set to
+ * true, will cause DataTables to defer the creation of the table elements for
+ * each row until they are needed for a draw - saving a significant amount of
+ * time.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.deferRender
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "ajax": "sources/arrays.txt",
+ * "deferRender": true
+ * } );
+ * } );
+ */
+ "bDeferRender": false,
+
+
+ /**
+ * Replace a DataTable which matches the given selector and replace it with
+ * one which has the properties of the new initialisation object passed. If no
+ * table matches the selector, then the new DataTable will be constructed as
+ * per normal.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.destroy
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "srollY": "200px",
+ * "paginate": false
+ * } );
+ *
+ * // Some time later....
+ * $('#example').dataTable( {
+ * "filter": false,
+ * "destroy": true
+ * } );
+ * } );
+ */
+ "bDestroy": false,
+
+
+ /**
+ * Enable or disable filtering of data. Filtering in DataTables is "smart" in
+ * that it allows the end user to input multiple words (space separated) and
+ * will match a row containing those words, even if not in the order that was
+ * specified (this allow matching across multiple columns). Note that if you
+ * wish to use filtering in DataTables this must remain 'true' - to remove the
+ * default filtering input box and retain filtering abilities, please use
+ * {@link DataTable.defaults.dom}.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.searching
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "searching": false
+ * } );
+ * } );
+ */
+ "bFilter": true,
+
+
+ /**
+ * Enable or disable the table information display. This shows information
+ * about the data that is currently visible on the page, including information
+ * about filtered data if that action is being performed.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.info
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "info": false
+ * } );
+ * } );
+ */
+ "bInfo": true,
+
+
+ /**
+ * Enable jQuery UI ThemeRoller support (required as ThemeRoller requires some
+ * slightly different and additional mark-up from what DataTables has
+ * traditionally used).
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.jQueryUI
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "jQueryUI": true
+ * } );
+ * } );
+ */
+ "bJQueryUI": false,
+
+
+ /**
+ * Allows the end user to select the size of a formatted page from a select
+ * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`).
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.lengthChange
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "lengthChange": false
+ * } );
+ * } );
+ */
+ "bLengthChange": true,
+
+
+ /**
+ * Enable or disable pagination.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.paging
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "paging": false
+ * } );
+ * } );
+ */
+ "bPaginate": true,
+
+
+ /**
+ * Enable or disable the display of a 'processing' indicator when the table is
+ * being processed (e.g. a sort). This is particularly useful for tables with
+ * large amounts of data where it can take a noticeable amount of time to sort
+ * the entries.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.processing
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "processing": true
+ * } );
+ * } );
+ */
+ "bProcessing": false,
+
+
+ /**
+ * Retrieve the DataTables object for the given selector. Note that if the
+ * table has already been initialised, this parameter will cause DataTables
+ * to simply return the object that has already been set up - it will not take
+ * account of any changes you might have made to the initialisation object
+ * passed to DataTables (setting this parameter to true is an acknowledgement
+ * that you understand this). `destroy` can be used to reinitialise a table if
+ * you need.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.retrieve
+ *
+ * @example
+ * $(document).ready( function() {
+ * initTable();
+ * tableActions();
+ * } );
+ *
+ * function initTable ()
+ * {
+ * return $('#example').dataTable( {
+ * "scrollY": "200px",
+ * "paginate": false,
+ * "retrieve": true
+ * } );
+ * }
+ *
+ * function tableActions ()
+ * {
+ * var table = initTable();
+ * // perform API operations with oTable
+ * }
+ */
+ "bRetrieve": false,
+
+
+ /**
+ * When vertical (y) scrolling is enabled, DataTables will force the height of
+ * the table's viewport to the given height at all times (useful for layout).
+ * However, this can look odd when filtering data down to a small data set,
+ * and the footer is left "floating" further down. This parameter (when
+ * enabled) will cause DataTables to collapse the table's viewport down when
+ * the result set will fit within the given Y height.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.scrollCollapse
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "scrollY": "200",
+ * "scrollCollapse": true
+ * } );
+ * } );
+ */
+ "bScrollCollapse": false,
+
+
+ /**
+ * Configure DataTables to use server-side processing. Note that the
+ * `ajax` parameter must also be given in order to give DataTables a
+ * source to obtain the required data for each draw.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Features
+ * @dtopt Server-side
+ * @name DataTable.defaults.serverSide
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "serverSide": true,
+ * "ajax": "xhr.php"
+ * } );
+ * } );
+ */
+ "bServerSide": false,
+
+
+ /**
+ * Enable or disable sorting of columns. Sorting of individual columns can be
+ * disabled by the `sortable` option for each column.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.ordering
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "ordering": false
+ * } );
+ * } );
+ */
+ "bSort": true,
+
+
+ /**
+ * Enable or display DataTables' ability to sort multiple columns at the
+ * same time (activated by shift-click by the user).
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.orderMulti
+ *
+ * @example
+ * // Disable multiple column sorting ability
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "orderMulti": false
+ * } );
+ * } );
+ */
+ "bSortMulti": true,
+
+
+ /**
+ * Allows control over whether DataTables should use the top (true) unique
+ * cell that is found for a single column, or the bottom (false - default).
+ * This is useful when using complex headers.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.orderCellsTop
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "orderCellsTop": true
+ * } );
+ * } );
+ */
+ "bSortCellsTop": false,
+
+
+ /**
+ * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
+ * `sorting\_3` to the columns which are currently being sorted on. This is
+ * presented as a feature switch as it can increase processing time (while
+ * classes are removed and added) so for large data sets you might want to
+ * turn this off.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.orderClasses
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "orderClasses": false
+ * } );
+ * } );
+ */
+ "bSortClasses": true,
+
+
+ /**
+ * Enable or disable state saving. When enabled HTML5 `localStorage` will be
+ * used to save table display information such as pagination information,
+ * display length, filtering and sorting. As such when the end user reloads
+ * the page the display display will match what thy had previously set up.
+ *
+ * Due to the use of `localStorage` the default state saving is not supported
+ * in IE6 or 7. If state saving is required in those browsers, use
+ * `stateSaveCallback` to provide a storage solution such as cookies.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.stateSave
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "stateSave": true
+ * } );
+ * } );
+ */
+ "bStateSave": false,
+
+
+ /**
+ * This function is called when a TR element is created (and all TD child
+ * elements have been inserted), or registered if using a DOM source, allowing
+ * manipulation of the TR element (adding classes etc).
+ * @type function
+ * @param {node} row "TR" element for the current row
+ * @param {array} data Raw data array for this row
+ * @param {int} dataIndex The index of this row in the internal aoData array
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.createdRow
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "createdRow": function( row, data, dataIndex ) {
+ * // Bold the grade for all 'A' grade browsers
+ * if ( data[4] == "A" )
+ * {
+ * $('td:eq(4)', row).html( 'A ' );
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "fnCreatedRow": null,
+
+
+ /**
+ * This function is called on every 'draw' event, and allows you to
+ * dynamically modify any aspect you want about the created DOM.
+ * @type function
+ * @param {object} settings DataTables settings object
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.drawCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "drawCallback": function( settings ) {
+ * alert( 'DataTables has redrawn the table' );
+ * }
+ * } );
+ * } );
+ */
+ "fnDrawCallback": null,
+
+
+ /**
+ * Identical to fnHeaderCallback() but for the table footer this function
+ * allows you to modify the table footer on every 'draw' event.
+ * @type function
+ * @param {node} foot "TR" element for the footer
+ * @param {array} data Full table data (as derived from the original HTML)
+ * @param {int} start Index for the current display starting point in the
+ * display array
+ * @param {int} end Index for the current display ending point in the
+ * display array
+ * @param {array int} display Index array to translate the visual position
+ * to the full data array
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.footerCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "footerCallback": function( tfoot, data, start, end, display ) {
+ * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start;
+ * }
+ * } );
+ * } )
+ */
+ "fnFooterCallback": null,
+
+
+ /**
+ * When rendering large numbers in the information element for the table
+ * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers
+ * to have a comma separator for the 'thousands' units (e.g. 1 million is
+ * rendered as "1,000,000") to help readability for the end user. This
+ * function will override the default method DataTables uses.
+ * @type function
+ * @member
+ * @param {int} toFormat number to be formatted
+ * @returns {string} formatted string for DataTables to show the number
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.formatNumber
+ *
+ * @example
+ * // Format a number using a single quote for the separator (note that
+ * // this can also be done with the language.thousands option)
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "formatNumber": function ( toFormat ) {
+ * return toFormat.toString().replace(
+ * /\B(?=(\d{3})+(?!\d))/g, "'"
+ * );
+ * };
+ * } );
+ * } );
+ */
+ "fnFormatNumber": function ( toFormat ) {
+ return toFormat.toString().replace(
+ /\B(?=(\d{3})+(?!\d))/g,
+ this.oLanguage.sThousands
+ );
+ },
+
+
+ /**
+ * This function is called on every 'draw' event, and allows you to
+ * dynamically modify the header row. This can be used to calculate and
+ * display useful information about the table.
+ * @type function
+ * @param {node} head "TR" element for the header
+ * @param {array} data Full table data (as derived from the original HTML)
+ * @param {int} start Index for the current display starting point in the
+ * display array
+ * @param {int} end Index for the current display ending point in the
+ * display array
+ * @param {array int} display Index array to translate the visual position
+ * to the full data array
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.headerCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "fheaderCallback": function( head, data, start, end, display ) {
+ * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records";
+ * }
+ * } );
+ * } )
+ */
+ "fnHeaderCallback": null,
+
+
+ /**
+ * The information element can be used to convey information about the current
+ * state of the table. Although the internationalisation options presented by
+ * DataTables are quite capable of dealing with most customisations, there may
+ * be times where you wish to customise the string further. This callback
+ * allows you to do exactly that.
+ * @type function
+ * @param {object} oSettings DataTables settings object
+ * @param {int} start Starting position in data for the draw
+ * @param {int} end End position in data for the draw
+ * @param {int} max Total number of rows in the table (regardless of
+ * filtering)
+ * @param {int} total Total number of rows in the data set, after filtering
+ * @param {string} pre The string that DataTables has formatted using it's
+ * own rules
+ * @returns {string} The string to be displayed in the information element.
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.infoCallback
+ *
+ * @example
+ * $('#example').dataTable( {
+ * "infoCallback": function( settings, start, end, max, total, pre ) {
+ * return start +" to "+ end;
+ * }
+ * } );
+ */
+ "fnInfoCallback": null,
+
+
+ /**
+ * Called when the table has been initialised. Normally DataTables will
+ * initialise sequentially and there will be no need for this function,
+ * however, this does not hold true when using external language information
+ * since that is obtained using an async XHR call.
+ * @type function
+ * @param {object} settings DataTables settings object
+ * @param {object} json The JSON object request from the server - only
+ * present if client-side Ajax sourced data is used
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.initComplete
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "initComplete": function(settings, json) {
+ * alert( 'DataTables has finished its initialisation.' );
+ * }
+ * } );
+ * } )
+ */
+ "fnInitComplete": null,
+
+
+ /**
+ * Called at the very start of each table draw and can be used to cancel the
+ * draw by returning false, any other return (including undefined) results in
+ * the full draw occurring).
+ * @type function
+ * @param {object} settings DataTables settings object
+ * @returns {boolean} False will cancel the draw, anything else (including no
+ * return) will allow it to complete.
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.preDrawCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "preDrawCallback": function( settings ) {
+ * if ( $('#test').val() == 1 ) {
+ * return false;
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "fnPreDrawCallback": null,
+
+
+ /**
+ * This function allows you to 'post process' each row after it have been
+ * generated for each table draw, but before it is rendered on screen. This
+ * function might be used for setting the row class name etc.
+ * @type function
+ * @param {node} row "TR" element for the current row
+ * @param {array} data Raw data array for this row
+ * @param {int} displayIndex The display index for the current table draw
+ * @param {int} displayIndexFull The index of the data in the full list of
+ * rows (after filtering)
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.rowCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "rowCallback": function( row, data, displayIndex, displayIndexFull ) {
+ * // Bold the grade for all 'A' grade browsers
+ * if ( data[4] == "A" ) {
+ * $('td:eq(4)', row).html( 'A ' );
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "fnRowCallback": null,
+
+
+ /**
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * This parameter allows you to override the default function which obtains
+ * the data from the server so something more suitable for your application.
+ * For example you could use POST data, or pull information from a Gears or
+ * AIR database.
+ * @type function
+ * @member
+ * @param {string} source HTTP source to obtain the data from (`ajax`)
+ * @param {array} data A key/value pair object containing the data to send
+ * to the server
+ * @param {function} callback to be called on completion of the data get
+ * process that will draw the data on the page.
+ * @param {object} settings DataTables settings object
+ *
+ * @dtopt Callbacks
+ * @dtopt Server-side
+ * @name DataTable.defaults.serverData
+ *
+ * @deprecated 1.10. Please use `ajax` for this functionality now.
+ */
+ "fnServerData": null,
+
+
+ /**
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * It is often useful to send extra data to the server when making an Ajax
+ * request - for example custom filtering information, and this callback
+ * function makes it trivial to send extra information to the server. The
+ * passed in parameter is the data set that has been constructed by
+ * DataTables, and you can add to this or modify it as you require.
+ * @type function
+ * @param {array} data Data array (array of objects which are name/value
+ * pairs) that has been constructed by DataTables and will be sent to the
+ * server. In the case of Ajax sourced data with server-side processing
+ * this will be an empty array, for server-side processing there will be a
+ * significant number of parameters!
+ * @returns {undefined} Ensure that you modify the data array passed in,
+ * as this is passed by reference.
+ *
+ * @dtopt Callbacks
+ * @dtopt Server-side
+ * @name DataTable.defaults.serverParams
+ *
+ * @deprecated 1.10. Please use `ajax` for this functionality now.
+ */
+ "fnServerParams": null,
+
+
+ /**
+ * Load the table state. With this function you can define from where, and how, the
+ * state of a table is loaded. By default DataTables will load from `localStorage`
+ * but you might wish to use a server-side database or cookies.
+ * @type function
+ * @member
+ * @param {object} settings DataTables settings object
+ * @return {object} The DataTables state object to be loaded
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.stateLoadCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateLoadCallback": function (settings) {
+ * var o;
+ *
+ * // Send an Ajax request to the server to get the data. Note that
+ * // this is a synchronous request.
+ * $.ajax( {
+ * "url": "/state_load",
+ * "async": false,
+ * "dataType": "json",
+ * "success": function (json) {
+ * o = json;
+ * }
+ * } );
+ *
+ * return o;
+ * }
+ * } );
+ * } );
+ */
+ "fnStateLoadCallback": function ( settings ) {
+ try {
+ return JSON.parse(
+ (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem(
+ 'DataTables_'+settings.sInstance+'_'+location.pathname
+ )
+ );
+ } catch (e) {}
+ },
+
+
+ /**
+ * Callback which allows modification of the saved state prior to loading that state.
+ * This callback is called when the table is loading state from the stored data, but
+ * prior to the settings object being modified by the saved state. Note that for
+ * plug-in authors, you should use the `stateLoadParams` event to load parameters for
+ * a plug-in.
+ * @type function
+ * @param {object} settings DataTables settings object
+ * @param {object} data The state object that is to be loaded
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.stateLoadParams
+ *
+ * @example
+ * // Remove a saved filter, so filtering is never loaded
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateLoadParams": function (settings, data) {
+ * data.oSearch.sSearch = "";
+ * }
+ * } );
+ * } );
+ *
+ * @example
+ * // Disallow state loading by returning false
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateLoadParams": function (settings, data) {
+ * return false;
+ * }
+ * } );
+ * } );
+ */
+ "fnStateLoadParams": null,
+
+
+ /**
+ * Callback that is called when the state has been loaded from the state saving method
+ * and the DataTables settings object has been modified as a result of the loaded state.
+ * @type function
+ * @param {object} settings DataTables settings object
+ * @param {object} data The state object that was loaded
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.stateLoaded
+ *
+ * @example
+ * // Show an alert with the filtering value that was saved
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateLoaded": function (settings, data) {
+ * alert( 'Saved filter was: '+data.oSearch.sSearch );
+ * }
+ * } );
+ * } );
+ */
+ "fnStateLoaded": null,
+
+
+ /**
+ * Save the table state. This function allows you to define where and how the state
+ * information for the table is stored By default DataTables will use `localStorage`
+ * but you might wish to use a server-side database or cookies.
+ * @type function
+ * @member
+ * @param {object} settings DataTables settings object
+ * @param {object} data The state object to be saved
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.stateSaveCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateSaveCallback": function (settings, data) {
+ * // Send an Ajax request to the server with the state object
+ * $.ajax( {
+ * "url": "/state_save",
+ * "data": data,
+ * "dataType": "json",
+ * "method": "POST"
+ * "success": function () {}
+ * } );
+ * }
+ * } );
+ * } );
+ */
+ "fnStateSaveCallback": function ( settings, data ) {
+ try {
+ (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem(
+ 'DataTables_'+settings.sInstance+'_'+location.pathname,
+ JSON.stringify( data )
+ );
+ } catch (e) {}
+ },
+
+
+ /**
+ * Callback which allows modification of the state to be saved. Called when the table
+ * has changed state a new state save is required. This method allows modification of
+ * the state saving object prior to actually doing the save, including addition or
+ * other state properties or modification. Note that for plug-in authors, you should
+ * use the `stateSaveParams` event to save parameters for a plug-in.
+ * @type function
+ * @param {object} settings DataTables settings object
+ * @param {object} data The state object to be saved
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.stateSaveParams
+ *
+ * @example
+ * // Remove a saved filter, so filtering is never saved
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateSaveParams": function (settings, data) {
+ * data.oSearch.sSearch = "";
+ * }
+ * } );
+ * } );
+ */
+ "fnStateSaveParams": null,
+
+
+ /**
+ * Duration for which the saved state information is considered valid. After this period
+ * has elapsed the state will be returned to the default.
+ * Value is given in seconds.
+ * @type int
+ * @default 7200 (2 hours)
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.stateDuration
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateDuration": 60*60*24; // 1 day
+ * } );
+ * } )
+ */
+ "iStateDuration": 7200,
+
+
+ /**
+ * When enabled DataTables will not make a request to the server for the first
+ * page draw - rather it will use the data already on the page (no sorting etc
+ * will be applied to it), thus saving on an XHR at load time. `deferLoading`
+ * is used to indicate that deferred loading is required, but it is also used
+ * to tell DataTables how many records there are in the full table (allowing
+ * the information element and pagination to be displayed correctly). In the case
+ * where a filtering is applied to the table on initial load, this can be
+ * indicated by giving the parameter as an array, where the first element is
+ * the number of records available after filtering and the second element is the
+ * number of records without filtering (allowing the table information element
+ * to be shown correctly).
+ * @type int | array
+ * @default null
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.deferLoading
+ *
+ * @example
+ * // 57 records available in the table, no filtering applied
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "serverSide": true,
+ * "ajax": "scripts/server_processing.php",
+ * "deferLoading": 57
+ * } );
+ * } );
+ *
+ * @example
+ * // 57 records after filtering, 100 without filtering (an initial filter applied)
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "serverSide": true,
+ * "ajax": "scripts/server_processing.php",
+ * "deferLoading": [ 57, 100 ],
+ * "search": {
+ * "search": "my_filter"
+ * }
+ * } );
+ * } );
+ */
+ "iDeferLoading": null,
+
+
+ /**
+ * Number of rows to display on a single page when using pagination. If
+ * feature enabled (`lengthChange`) then the end user will be able to override
+ * this to a custom setting using a pop-up menu.
+ * @type int
+ * @default 10
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.pageLength
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "pageLength": 50
+ * } );
+ * } )
+ */
+ "iDisplayLength": 10,
+
+
+ /**
+ * Define the starting point for data display when using DataTables with
+ * pagination. Note that this parameter is the number of records, rather than
+ * the page number, so if you have 10 records per page and want to start on
+ * the third page, it should be "20".
+ * @type int
+ * @default 0
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.displayStart
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "displayStart": 20
+ * } );
+ * } )
+ */
+ "iDisplayStart": 0,
+
+
+ /**
+ * By default DataTables allows keyboard navigation of the table (sorting, paging,
+ * and filtering) by adding a `tabindex` attribute to the required elements. This
+ * allows you to tab through the controls and press the enter key to activate them.
+ * The tabindex is default 0, meaning that the tab follows the flow of the document.
+ * You can overrule this using this parameter if you wish. Use a value of -1 to
+ * disable built-in keyboard navigation.
+ * @type int
+ * @default 0
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.tabIndex
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "tabIndex": 1
+ * } );
+ * } );
+ */
+ "iTabIndex": 0,
+
+
+ /**
+ * Classes that DataTables assigns to the various components and features
+ * that it adds to the HTML table. This allows classes to be configured
+ * during initialisation in addition to through the static
+ * {@link DataTable.ext.oStdClasses} object).
+ * @namespace
+ * @name DataTable.defaults.classes
+ */
+ "oClasses": {},
+
+
+ /**
+ * All strings that DataTables uses in the user interface that it creates
+ * are defined in this object, allowing you to modified them individually or
+ * completely replace them all as required.
+ * @namespace
+ * @name DataTable.defaults.language
+ */
+ "oLanguage": {
+ /**
+ * Strings that are used for WAI-ARIA labels and controls only (these are not
+ * actually visible on the page, but will be read by screenreaders, and thus
+ * must be internationalised as well).
+ * @namespace
+ * @name DataTable.defaults.language.aria
+ */
+ "oAria": {
+ /**
+ * ARIA label that is added to the table headers when the column may be
+ * sorted ascending by activing the column (click or return when focused).
+ * Note that the column header is prefixed to this string.
+ * @type string
+ * @default : activate to sort column ascending
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.aria.sortAscending
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "aria": {
+ * "sortAscending": " - click/return to sort ascending"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sSortAscending": ": activate to sort column ascending",
+
+ /**
+ * ARIA label that is added to the table headers when the column may be
+ * sorted descending by activing the column (click or return when focused).
+ * Note that the column header is prefixed to this string.
+ * @type string
+ * @default : activate to sort column ascending
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.aria.sortDescending
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "aria": {
+ * "sortDescending": " - click/return to sort descending"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sSortDescending": ": activate to sort column descending"
+ },
+
+ /**
+ * Pagination string used by DataTables for the built-in pagination
+ * control types.
+ * @namespace
+ * @name DataTable.defaults.language.paginate
+ */
+ "oPaginate": {
+ /**
+ * Text to use when using the 'full_numbers' type of pagination for the
+ * button to take the user to the first page.
+ * @type string
+ * @default First
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.paginate.first
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "paginate": {
+ * "first": "First page"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sFirst": "First",
+
+
+ /**
+ * Text to use when using the 'full_numbers' type of pagination for the
+ * button to take the user to the last page.
+ * @type string
+ * @default Last
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.paginate.last
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "paginate": {
+ * "last": "Last page"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sLast": "Last",
+
+
+ /**
+ * Text to use for the 'next' pagination button (to take the user to the
+ * next page).
+ * @type string
+ * @default Next
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.paginate.next
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "paginate": {
+ * "next": "Next page"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sNext": "Next",
+
+
+ /**
+ * Text to use for the 'previous' pagination button (to take the user to
+ * the previous page).
+ * @type string
+ * @default Previous
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.paginate.previous
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "paginate": {
+ * "previous": "Previous page"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sPrevious": "Previous"
+ },
+
+ /**
+ * This string is shown in preference to `zeroRecords` when the table is
+ * empty of data (regardless of filtering). Note that this is an optional
+ * parameter - if it is not given, the value of `zeroRecords` will be used
+ * instead (either the default or given value).
+ * @type string
+ * @default No data available in table
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.emptyTable
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "emptyTable": "No data available in table"
+ * }
+ * } );
+ * } );
+ */
+ "sEmptyTable": "No data available in table",
+
+
+ /**
+ * This string gives information to the end user about the information
+ * that is current on display on the page. The following tokens can be
+ * used in the string and will be dynamically replaced as the table
+ * display updates. This tokens can be placed anywhere in the string, or
+ * removed as needed by the language requires:
+ *
+ * * `\_START\_` - Display index of the first record on the current page
+ * * `\_END\_` - Display index of the last record on the current page
+ * * `\_TOTAL\_` - Number of records in the table after filtering
+ * * `\_MAX\_` - Number of records in the table without filtering
+ * * `\_PAGE\_` - Current page number
+ * * `\_PAGES\_` - Total number of pages of data in the table
+ *
+ * @type string
+ * @default Showing _START_ to _END_ of _TOTAL_ entries
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.info
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "info": "Showing page _PAGE_ of _PAGES_"
+ * }
+ * } );
+ * } );
+ */
+ "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
+
+
+ /**
+ * Display information string for when the table is empty. Typically the
+ * format of this string should match `info`.
+ * @type string
+ * @default Showing 0 to 0 of 0 entries
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.infoEmpty
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "infoEmpty": "No entries to show"
+ * }
+ * } );
+ * } );
+ */
+ "sInfoEmpty": "Showing 0 to 0 of 0 entries",
+
+
+ /**
+ * When a user filters the information in a table, this string is appended
+ * to the information (`info`) to give an idea of how strong the filtering
+ * is. The variable _MAX_ is dynamically updated.
+ * @type string
+ * @default (filtered from _MAX_ total entries)
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.infoFiltered
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "infoFiltered": " - filtering from _MAX_ records"
+ * }
+ * } );
+ * } );
+ */
+ "sInfoFiltered": "(filtered from _MAX_ total entries)",
+
+
+ /**
+ * If can be useful to append extra information to the info string at times,
+ * and this variable does exactly that. This information will be appended to
+ * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are
+ * being used) at all times.
+ * @type string
+ * @default Empty string
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.infoPostFix
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "infoPostFix": "All records shown are derived from real information."
+ * }
+ * } );
+ * } );
+ */
+ "sInfoPostFix": "",
+
+
+ /**
+ * This decimal place operator is a little different from the other
+ * language options since DataTables doesn't output floating point
+ * numbers, so it won't ever use this for display of a number. Rather,
+ * what this parameter does is modify the sort methods of the table so
+ * that numbers which are in a format which has a character other than
+ * a period (`.`) as a decimal place will be sorted numerically.
+ *
+ * Note that numbers with different decimal places cannot be shown in
+ * the same table and still be sortable, the table must be consistent.
+ * However, multiple different tables on the page can use different
+ * decimal place characters.
+ * @type string
+ * @default
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.decimal
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "decimal": ","
+ * "thousands": "."
+ * }
+ * } );
+ * } );
+ */
+ "sDecimal": "",
+
+
+ /**
+ * DataTables has a build in number formatter (`formatNumber`) which is
+ * used to format large numbers that are used in the table information.
+ * By default a comma is used, but this can be trivially changed to any
+ * character you wish with this parameter.
+ * @type string
+ * @default ,
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.thousands
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "thousands": "'"
+ * }
+ * } );
+ * } );
+ */
+ "sThousands": ",",
+
+
+ /**
+ * Detail the action that will be taken when the drop down menu for the
+ * pagination length option is changed. The '_MENU_' variable is replaced
+ * with a default select list of 10, 25, 50 and 100, and can be replaced
+ * with a custom select box if required.
+ * @type string
+ * @default Show _MENU_ entries
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.lengthMenu
+ *
+ * @example
+ * // Language change only
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "lengthMenu": "Display _MENU_ records"
+ * }
+ * } );
+ * } );
+ *
+ * @example
+ * // Language and options change
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "lengthMenu": 'Display '+
+ * '10 '+
+ * '20 '+
+ * '30 '+
+ * '40 '+
+ * '50 '+
+ * 'All '+
+ * ' records'
+ * }
+ * } );
+ * } );
+ */
+ "sLengthMenu": "Show _MENU_ entries",
+
+
+ /**
+ * When using Ajax sourced data and during the first draw when DataTables is
+ * gathering the data, this message is shown in an empty row in the table to
+ * indicate to the end user the the data is being loaded. Note that this
+ * parameter is not used when loading data by server-side processing, just
+ * Ajax sourced data with client-side processing.
+ * @type string
+ * @default Loading...
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.loadingRecords
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "loadingRecords": "Please wait - loading..."
+ * }
+ * } );
+ * } );
+ */
+ "sLoadingRecords": "Loading...",
+
+
+ /**
+ * Text which is displayed when the table is processing a user action
+ * (usually a sort command or similar).
+ * @type string
+ * @default Processing...
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.processing
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "processing": "DataTables is currently busy"
+ * }
+ * } );
+ * } );
+ */
+ "sProcessing": "Processing...",
+
+
+ /**
+ * Details the actions that will be taken when the user types into the
+ * filtering input text box. The variable "_INPUT_", if used in the string,
+ * is replaced with the HTML text box for the filtering input allowing
+ * control over where it appears in the string. If "_INPUT_" is not given
+ * then the input box is appended to the string automatically.
+ * @type string
+ * @default Search:
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.search
+ *
+ * @example
+ * // Input text box will be appended at the end automatically
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "search": "Filter records:"
+ * }
+ * } );
+ * } );
+ *
+ * @example
+ * // Specify where the filter should appear
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "search": "Apply filter _INPUT_ to table"
+ * }
+ * } );
+ * } );
+ */
+ "sSearch": "Search:",
+
+
+ /**
+ * Assign a `placeholder` attribute to the search `input` element
+ * @type string
+ * @default
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.searchPlaceholder
+ */
+ "sSearchPlaceholder": "",
+
+
+ /**
+ * All of the language information can be stored in a file on the
+ * server-side, which DataTables will look up if this parameter is passed.
+ * It must store the URL of the language file, which is in a JSON format,
+ * and the object has the same properties as the oLanguage object in the
+ * initialiser object (i.e. the above parameters). Please refer to one of
+ * the example language files to see how this works in action.
+ * @type string
+ * @default Empty string - i.e. disabled
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.url
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "url": "http://www.sprymedia.co.uk/dataTables/lang.txt"
+ * }
+ * } );
+ * } );
+ */
+ "sUrl": "",
+
+
+ /**
+ * Text shown inside the table records when the is no information to be
+ * displayed after filtering. `emptyTable` is shown when there is simply no
+ * information in the table at all (regardless of filtering).
+ * @type string
+ * @default No matching records found
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.zeroRecords
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "zeroRecords": "No records to display"
+ * }
+ * } );
+ * } );
+ */
+ "sZeroRecords": "No matching records found"
+ },
+
+
+ /**
+ * This parameter allows you to have define the global filtering state at
+ * initialisation time. As an object the `search` parameter must be
+ * defined, but all other parameters are optional. When `regex` is true,
+ * the search string will be treated as a regular expression, when false
+ * (default) it will be treated as a straight string. When `smart`
+ * DataTables will use it's smart filtering methods (to word match at
+ * any point in the data), when false this will not be done.
+ * @namespace
+ * @extends DataTable.models.oSearch
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.search
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "search": {"search": "Initial search"}
+ * } );
+ * } )
+ */
+ "oSearch": $.extend( {}, DataTable.models.oSearch ),
+
+
+ /**
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * By default DataTables will look for the property `data` (or `aaData` for
+ * compatibility with DataTables 1.9-) when obtaining data from an Ajax
+ * source or for server-side processing - this parameter allows that
+ * property to be changed. You can use Javascript dotted object notation to
+ * get a data source for multiple levels of nesting.
+ * @type string
+ * @default data
+ *
+ * @dtopt Options
+ * @dtopt Server-side
+ * @name DataTable.defaults.ajaxDataProp
+ *
+ * @deprecated 1.10. Please use `ajax` for this functionality now.
+ */
+ "sAjaxDataProp": "data",
+
+
+ /**
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * You can instruct DataTables to load data from an external
+ * source using this parameter (use aData if you want to pass data in you
+ * already have). Simply provide a url a JSON object can be obtained from.
+ * @type string
+ * @default null
+ *
+ * @dtopt Options
+ * @dtopt Server-side
+ * @name DataTable.defaults.ajaxSource
+ *
+ * @deprecated 1.10. Please use `ajax` for this functionality now.
+ */
+ "sAjaxSource": null,
+
+
+ /**
+ * This initialisation variable allows you to specify exactly where in the
+ * DOM you want DataTables to inject the various controls it adds to the page
+ * (for example you might want the pagination controls at the top of the
+ * table). DIV elements (with or without a custom class) can also be added to
+ * aid styling. The follow syntax is used:
+ *
+ * The following options are allowed:
+ *
+ * 'l' - Length changing
+ * 'f' - Filtering input
+ * 't' - The table!
+ * 'i' - Information
+ * 'p' - Pagination
+ * 'r' - pRocessing
+ *
+ *
+ * The following constants are allowed:
+ *
+ * 'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')
+ * 'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')
+ *
+ *
+ * The following syntax is expected:
+ *
+ * '<' and '>' - div elements
+ * '<"class" and '>' - div with a class
+ * '<"#id" and '>' - div with an ID
+ *
+ *
+ * Examples:
+ *
+ * '<"wrapper"flipt>'
+ * '<lf<t>ip>'
+ *
+ *
+ *
+ * @type string
+ * @default lfrtip (when `jQueryUI` is false) or
+ * <"H"lfr>t<"F"ip> (when `jQueryUI` is true)
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.dom
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "dom": '<"top"i>rt<"bottom"flp><"clear">'
+ * } );
+ * } );
+ */
+ "sDom": "lfrtip",
+
+
+ /**
+ * Search delay option. This will throttle full table searches that use the
+ * DataTables provided search input element (it does not effect calls to
+ * `dt-api search()`, providing a delay before the search is made.
+ * @type integer
+ * @default 0
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.searchDelay
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "searchDelay": 200
+ * } );
+ * } )
+ */
+ "searchDelay": null,
+
+
+ /**
+ * DataTables features four different built-in options for the buttons to
+ * display for pagination control:
+ *
+ * * `simple` - 'Previous' and 'Next' buttons only
+ * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers
+ * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons
+ * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus
+ * page numbers
+ *
+ * Further methods can be added using {@link DataTable.ext.oPagination}.
+ * @type string
+ * @default simple_numbers
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.pagingType
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "pagingType": "full_numbers"
+ * } );
+ * } )
+ */
+ "sPaginationType": "simple_numbers",
+
+
+ /**
+ * Enable horizontal scrolling. When a table is too wide to fit into a
+ * certain layout, or you have a large number of columns in the table, you
+ * can enable x-scrolling to show the table in a viewport, which can be
+ * scrolled. This property can be `true` which will allow the table to
+ * scroll horizontally when needed, or any CSS unit, or a number (in which
+ * case it will be treated as a pixel measurement). Setting as simply `true`
+ * is recommended.
+ * @type boolean|string
+ * @default blank string - i.e. disabled
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.scrollX
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "scrollX": true,
+ * "scrollCollapse": true
+ * } );
+ * } );
+ */
+ "sScrollX": "",
+
+
+ /**
+ * This property can be used to force a DataTable to use more width than it
+ * might otherwise do when x-scrolling is enabled. For example if you have a
+ * table which requires to be well spaced, this parameter is useful for
+ * "over-sizing" the table, and thus forcing scrolling. This property can by
+ * any CSS unit, or a number (in which case it will be treated as a pixel
+ * measurement).
+ * @type string
+ * @default blank string - i.e. disabled
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.scrollXInner
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "scrollX": "100%",
+ * "scrollXInner": "110%"
+ * } );
+ * } );
+ */
+ "sScrollXInner": "",
+
+
+ /**
+ * Enable vertical scrolling. Vertical scrolling will constrain the DataTable
+ * to the given height, and enable scrolling for any data which overflows the
+ * current viewport. This can be used as an alternative to paging to display
+ * a lot of data in a small area (although paging and scrolling can both be
+ * enabled at the same time). This property can be any CSS unit, or a number
+ * (in which case it will be treated as a pixel measurement).
+ * @type string
+ * @default blank string - i.e. disabled
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.scrollY
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "scrollY": "200px",
+ * "paginate": false
+ * } );
+ * } );
+ */
+ "sScrollY": "",
+
+
+ /**
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * Set the HTTP method that is used to make the Ajax call for server-side
+ * processing or Ajax sourced data.
+ * @type string
+ * @default GET
+ *
+ * @dtopt Options
+ * @dtopt Server-side
+ * @name DataTable.defaults.serverMethod
+ *
+ * @deprecated 1.10. Please use `ajax` for this functionality now.
+ */
+ "sServerMethod": "GET",
+
+
+ /**
+ * DataTables makes use of renderers when displaying HTML elements for
+ * a table. These renderers can be added or modified by plug-ins to
+ * generate suitable mark-up for a site. For example the Bootstrap
+ * integration plug-in for DataTables uses a paging button renderer to
+ * display pagination buttons in the mark-up required by Bootstrap.
+ *
+ * For further information about the renderers available see
+ * DataTable.ext.renderer
+ * @type string|object
+ * @default null
+ *
+ * @name DataTable.defaults.renderer
+ *
+ */
+ "renderer": null,
+
+
+ /**
+ * Set the data property name that DataTables should use to get a row's id
+ * to set as the `id` property in the node.
+ * @type string
+ * @default DT_RowId
+ *
+ * @name DataTable.defaults.rowId
+ */
+ "rowId": "DT_RowId"
+ };
+
+ _fnHungarianMap( DataTable.defaults );
+
+
+
+ /*
+ * Developer note - See note in model.defaults.js about the use of Hungarian
+ * notation and camel case.
+ */
+
+ /**
+ * Column options that can be given to DataTables at initialisation time.
+ * @namespace
+ */
+ DataTable.defaults.column = {
+ /**
+ * Define which column(s) an order will occur on for this column. This
+ * allows a column's ordering to take multiple columns into account when
+ * doing a sort or use the data from a different column. For example first
+ * name / last name columns make sense to do a multi-column sort over the
+ * two columns.
+ * @type array|int
+ * @default null Takes the value of the column index automatically
+ *
+ * @name DataTable.defaults.column.orderData
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "orderData": [ 0, 1 ], "targets": [ 0 ] },
+ * { "orderData": [ 1, 0 ], "targets": [ 1 ] },
+ * { "orderData": 2, "targets": [ 2 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "orderData": [ 0, 1 ] },
+ * { "orderData": [ 1, 0 ] },
+ * { "orderData": 2 },
+ * null,
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "aDataSort": null,
+ "iDataSort": -1,
+
+
+ /**
+ * You can control the default ordering direction, and even alter the
+ * behaviour of the sort handler (i.e. only allow ascending ordering etc)
+ * using this parameter.
+ * @type array
+ * @default [ 'asc', 'desc' ]
+ *
+ * @name DataTable.defaults.column.orderSequence
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "orderSequence": [ "asc" ], "targets": [ 1 ] },
+ * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] },
+ * { "orderSequence": [ "desc" ], "targets": [ 3 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * null,
+ * { "orderSequence": [ "asc" ] },
+ * { "orderSequence": [ "desc", "asc", "asc" ] },
+ * { "orderSequence": [ "desc" ] },
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "asSorting": [ 'asc', 'desc' ],
+
+
+ /**
+ * Enable or disable filtering on the data in this column.
+ * @type boolean
+ * @default true
+ *
+ * @name DataTable.defaults.column.searchable
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "searchable": false, "targets": [ 0 ] }
+ * ] } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "searchable": false },
+ * null,
+ * null,
+ * null,
+ * null
+ * ] } );
+ * } );
+ */
+ "bSearchable": true,
+
+
+ /**
+ * Enable or disable ordering on this column.
+ * @type boolean
+ * @default true
+ *
+ * @name DataTable.defaults.column.orderable
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "orderable": false, "targets": [ 0 ] }
+ * ] } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "orderable": false },
+ * null,
+ * null,
+ * null,
+ * null
+ * ] } );
+ * } );
+ */
+ "bSortable": true,
+
+
+ /**
+ * Enable or disable the display of this column.
+ * @type boolean
+ * @default true
+ *
+ * @name DataTable.defaults.column.visible
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "visible": false, "targets": [ 0 ] }
+ * ] } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "visible": false },
+ * null,
+ * null,
+ * null,
+ * null
+ * ] } );
+ * } );
+ */
+ "bVisible": true,
+
+
+ /**
+ * Developer definable function that is called whenever a cell is created (Ajax source,
+ * etc) or processed for input (DOM source). This can be used as a compliment to mRender
+ * allowing you to modify the DOM element (add background colour for example) when the
+ * element is available.
+ * @type function
+ * @param {element} td The TD node that has been created
+ * @param {*} cellData The Data for the cell
+ * @param {array|object} rowData The data for the whole row
+ * @param {int} row The row index for the aoData data store
+ * @param {int} col The column index for aoColumns
+ *
+ * @name DataTable.defaults.column.createdCell
+ * @dtopt Columns
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [3],
+ * "createdCell": function (td, cellData, rowData, row, col) {
+ * if ( cellData == "1.7" ) {
+ * $(td).css('color', 'blue')
+ * }
+ * }
+ * } ]
+ * });
+ * } );
+ */
+ "fnCreatedCell": null,
+
+
+ /**
+ * This parameter has been replaced by `data` in DataTables to ensure naming
+ * consistency. `dataProp` can still be used, as there is backwards
+ * compatibility in DataTables for this option, but it is strongly
+ * recommended that you use `data` in preference to `dataProp`.
+ * @name DataTable.defaults.column.dataProp
+ */
+
+
+ /**
+ * This property can be used to read data from any data source property,
+ * including deeply nested objects / properties. `data` can be given in a
+ * number of different ways which effect its behaviour:
+ *
+ * * `integer` - treated as an array index for the data source. This is the
+ * default that DataTables uses (incrementally increased for each column).
+ * * `string` - read an object property from the data source. There are
+ * three 'special' options that can be used in the string to alter how
+ * DataTables reads the data from the source object:
+ * * `.` - Dotted Javascript notation. Just as you use a `.` in
+ * Javascript to read from nested objects, so to can the options
+ * specified in `data`. For example: `browser.version` or
+ * `browser.name`. If your object parameter name contains a period, use
+ * `\\` to escape it - i.e. `first\\.name`.
+ * * `[]` - Array notation. DataTables can automatically combine data
+ * from and array source, joining the data with the characters provided
+ * between the two brackets. For example: `name[, ]` would provide a
+ * comma-space separated list from the source array. If no characters
+ * are provided between the brackets, the original array source is
+ * returned.
+ * * `()` - Function notation. Adding `()` to the end of a parameter will
+ * execute a function of the name given. For example: `browser()` for a
+ * simple function on the data source, `browser.version()` for a
+ * function in a nested property or even `browser().version` to get an
+ * object property if the function called returns an object. Note that
+ * function notation is recommended for use in `render` rather than
+ * `data` as it is much simpler to use as a renderer.
+ * * `null` - use the original data source for the row rather than plucking
+ * data directly from it. This action has effects on two other
+ * initialisation options:
+ * * `defaultContent` - When null is given as the `data` option and
+ * `defaultContent` is specified for the column, the value defined by
+ * `defaultContent` will be used for the cell.
+ * * `render` - When null is used for the `data` option and the `render`
+ * option is specified for the column, the whole data source for the
+ * row is used for the renderer.
+ * * `function` - the function given will be executed whenever DataTables
+ * needs to set or get the data for a cell in the column. The function
+ * takes three parameters:
+ * * Parameters:
+ * * `{array|object}` The data source for the row
+ * * `{string}` The type call data requested - this will be 'set' when
+ * setting data or 'filter', 'display', 'type', 'sort' or undefined
+ * when gathering data. Note that when `undefined` is given for the
+ * type DataTables expects to get the raw data for the object back<
+ * * `{*}` Data to set when the second parameter is 'set'.
+ * * Return:
+ * * The return value from the function is not required when 'set' is
+ * the type of call, but otherwise the return is what will be used
+ * for the data requested.
+ *
+ * Note that `data` is a getter and setter option. If you just require
+ * formatting of data for output, you will likely want to use `render` which
+ * is simply a getter and thus simpler to use.
+ *
+ * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The
+ * name change reflects the flexibility of this property and is consistent
+ * with the naming of mRender. If 'mDataProp' is given, then it will still
+ * be used by DataTables, as it automatically maps the old name to the new
+ * if required.
+ *
+ * @type string|int|function|null
+ * @default null Use automatically calculated column index
+ *
+ * @name DataTable.defaults.column.data
+ * @dtopt Columns
+ *
+ * @example
+ * // Read table data from objects
+ * // JSON structure for each row:
+ * // {
+ * // "engine": {value},
+ * // "browser": {value},
+ * // "platform": {value},
+ * // "version": {value},
+ * // "grade": {value}
+ * // }
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "ajaxSource": "sources/objects.txt",
+ * "columns": [
+ * { "data": "engine" },
+ * { "data": "browser" },
+ * { "data": "platform" },
+ * { "data": "version" },
+ * { "data": "grade" }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Read information from deeply nested objects
+ * // JSON structure for each row:
+ * // {
+ * // "engine": {value},
+ * // "browser": {value},
+ * // "platform": {
+ * // "inner": {value}
+ * // },
+ * // "details": [
+ * // {value}, {value}
+ * // ]
+ * // }
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "ajaxSource": "sources/deep.txt",
+ * "columns": [
+ * { "data": "engine" },
+ * { "data": "browser" },
+ * { "data": "platform.inner" },
+ * { "data": "platform.details.0" },
+ * { "data": "platform.details.1" }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `data` as a function to provide different information for
+ * // sorting, filtering and display. In this case, currency (price)
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": function ( source, type, val ) {
+ * if (type === 'set') {
+ * source.price = val;
+ * // Store the computed dislay and filter values for efficiency
+ * source.price_display = val=="" ? "" : "$"+numberFormat(val);
+ * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val;
+ * return;
+ * }
+ * else if (type === 'display') {
+ * return source.price_display;
+ * }
+ * else if (type === 'filter') {
+ * return source.price_filter;
+ * }
+ * // 'sort', 'type' and undefined all just use the integer
+ * return source.price;
+ * }
+ * } ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using default content
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": null,
+ * "defaultContent": "Click to edit"
+ * } ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using array notation - outputting a list from an array
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": "name[, ]"
+ * } ]
+ * } );
+ * } );
+ *
+ */
+ "mData": null,
+
+
+ /**
+ * This property is the rendering partner to `data` and it is suggested that
+ * when you want to manipulate data for display (including filtering,
+ * sorting etc) without altering the underlying data for the table, use this
+ * property. `render` can be considered to be the the read only companion to
+ * `data` which is read / write (then as such more complex). Like `data`
+ * this option can be given in a number of different ways to effect its
+ * behaviour:
+ *
+ * * `integer` - treated as an array index for the data source. This is the
+ * default that DataTables uses (incrementally increased for each column).
+ * * `string` - read an object property from the data source. There are
+ * three 'special' options that can be used in the string to alter how
+ * DataTables reads the data from the source object:
+ * * `.` - Dotted Javascript notation. Just as you use a `.` in
+ * Javascript to read from nested objects, so to can the options
+ * specified in `data`. For example: `browser.version` or
+ * `browser.name`. If your object parameter name contains a period, use
+ * `\\` to escape it - i.e. `first\\.name`.
+ * * `[]` - Array notation. DataTables can automatically combine data
+ * from and array source, joining the data with the characters provided
+ * between the two brackets. For example: `name[, ]` would provide a
+ * comma-space separated list from the source array. If no characters
+ * are provided between the brackets, the original array source is
+ * returned.
+ * * `()` - Function notation. Adding `()` to the end of a parameter will
+ * execute a function of the name given. For example: `browser()` for a
+ * simple function on the data source, `browser.version()` for a
+ * function in a nested property or even `browser().version` to get an
+ * object property if the function called returns an object.
+ * * `object` - use different data for the different data types requested by
+ * DataTables ('filter', 'display', 'type' or 'sort'). The property names
+ * of the object is the data type the property refers to and the value can
+ * defined using an integer, string or function using the same rules as
+ * `render` normally does. Note that an `_` option _must_ be specified.
+ * This is the default value to use if you haven't specified a value for
+ * the data type requested by DataTables.
+ * * `function` - the function given will be executed whenever DataTables
+ * needs to set or get the data for a cell in the column. The function
+ * takes three parameters:
+ * * Parameters:
+ * * {array|object} The data source for the row (based on `data`)
+ * * {string} The type call data requested - this will be 'filter',
+ * 'display', 'type' or 'sort'.
+ * * {array|object} The full data source for the row (not based on
+ * `data`)
+ * * Return:
+ * * The return value from the function is what will be used for the
+ * data requested.
+ *
+ * @type string|int|function|object|null
+ * @default null Use the data source value.
+ *
+ * @name DataTable.defaults.column.render
+ * @dtopt Columns
+ *
+ * @example
+ * // Create a comma separated list from an array of objects
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "ajaxSource": "sources/deep.txt",
+ * "columns": [
+ * { "data": "engine" },
+ * { "data": "browser" },
+ * {
+ * "data": "platform",
+ * "render": "[, ].name"
+ * }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Execute a function to obtain data
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": null, // Use the full data source object for the renderer's source
+ * "render": "browserName()"
+ * } ]
+ * } );
+ * } );
+ *
+ * @example
+ * // As an object, extracting different data for the different types
+ * // This would be used with a data source such as:
+ * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" }
+ * // Here the `phone` integer is used for sorting and type detection, while `phone_filter`
+ * // (which has both forms) is used for filtering for if a user inputs either format, while
+ * // the formatted phone number is the one that is shown in the table.
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": null, // Use the full data source object for the renderer's source
+ * "render": {
+ * "_": "phone",
+ * "filter": "phone_filter",
+ * "display": "phone_display"
+ * }
+ * } ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Use as a function to create a link from the data source
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": "download_link",
+ * "render": function ( data, type, full ) {
+ * return 'Download ';
+ * }
+ * } ]
+ * } );
+ * } );
+ */
+ "mRender": null,
+
+
+ /**
+ * Change the cell type created for the column - either TD cells or TH cells. This
+ * can be useful as TH cells have semantic meaning in the table body, allowing them
+ * to act as a header for a row (you may wish to add scope='row' to the TH elements).
+ * @type string
+ * @default td
+ *
+ * @name DataTable.defaults.column.cellType
+ * @dtopt Columns
+ *
+ * @example
+ * // Make the first column use TH cells
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "cellType": "th"
+ * } ]
+ * } );
+ * } );
+ */
+ "sCellType": "td",
+
+
+ /**
+ * Class to give to each cell in this column.
+ * @type string
+ * @default Empty string
+ *
+ * @name DataTable.defaults.column.class
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "class": "my_class", "targets": [ 0 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "class": "my_class" },
+ * null,
+ * null,
+ * null,
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "sClass": "",
+
+ /**
+ * When DataTables calculates the column widths to assign to each column,
+ * it finds the longest string in each column and then constructs a
+ * temporary table and reads the widths from that. The problem with this
+ * is that "mmm" is much wider then "iiii", but the latter is a longer
+ * string - thus the calculation can go wrong (doing it properly and putting
+ * it into an DOM object and measuring that is horribly(!) slow). Thus as
+ * a "work around" we provide this option. It will append its value to the
+ * text that is found to be the longest string for the column - i.e. padding.
+ * Generally you shouldn't need this!
+ * @type string
+ * @default Empty string
+ *
+ * @name DataTable.defaults.column.contentPadding
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * null,
+ * null,
+ * null,
+ * {
+ * "contentPadding": "mmm"
+ * }
+ * ]
+ * } );
+ * } );
+ */
+ "sContentPadding": "",
+
+
+ /**
+ * Allows a default value to be given for a column's data, and will be used
+ * whenever a null data source is encountered (this can be because `data`
+ * is set to null, or because the data source itself is null).
+ * @type string
+ * @default null
+ *
+ * @name DataTable.defaults.column.defaultContent
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * {
+ * "data": null,
+ * "defaultContent": "Edit",
+ * "targets": [ -1 ]
+ * }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * null,
+ * null,
+ * null,
+ * {
+ * "data": null,
+ * "defaultContent": "Edit"
+ * }
+ * ]
+ * } );
+ * } );
+ */
+ "sDefaultContent": null,
+
+
+ /**
+ * This parameter is only used in DataTables' server-side processing. It can
+ * be exceptionally useful to know what columns are being displayed on the
+ * client side, and to map these to database fields. When defined, the names
+ * also allow DataTables to reorder information from the server if it comes
+ * back in an unexpected order (i.e. if you switch your columns around on the
+ * client-side, your server-side code does not also need updating).
+ * @type string
+ * @default Empty string
+ *
+ * @name DataTable.defaults.column.name
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "name": "engine", "targets": [ 0 ] },
+ * { "name": "browser", "targets": [ 1 ] },
+ * { "name": "platform", "targets": [ 2 ] },
+ * { "name": "version", "targets": [ 3 ] },
+ * { "name": "grade", "targets": [ 4 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "name": "engine" },
+ * { "name": "browser" },
+ * { "name": "platform" },
+ * { "name": "version" },
+ * { "name": "grade" }
+ * ]
+ * } );
+ * } );
+ */
+ "sName": "",
+
+
+ /**
+ * Defines a data source type for the ordering which can be used to read
+ * real-time information from the table (updating the internally cached
+ * version) prior to ordering. This allows ordering to occur on user
+ * editable elements such as form inputs.
+ * @type string
+ * @default std
+ *
+ * @name DataTable.defaults.column.orderDataType
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "orderDataType": "dom-text", "targets": [ 2, 3 ] },
+ * { "type": "numeric", "targets": [ 3 ] },
+ * { "orderDataType": "dom-select", "targets": [ 4 ] },
+ * { "orderDataType": "dom-checkbox", "targets": [ 5 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * null,
+ * null,
+ * { "orderDataType": "dom-text" },
+ * { "orderDataType": "dom-text", "type": "numeric" },
+ * { "orderDataType": "dom-select" },
+ * { "orderDataType": "dom-checkbox" }
+ * ]
+ * } );
+ * } );
+ */
+ "sSortDataType": "std",
+
+
+ /**
+ * The title of this column.
+ * @type string
+ * @default null Derived from the 'TH' value for this column in the
+ * original HTML table.
+ *
+ * @name DataTable.defaults.column.title
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "title": "My column title", "targets": [ 0 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "title": "My column title" },
+ * null,
+ * null,
+ * null,
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "sTitle": null,
+
+
+ /**
+ * The type allows you to specify how the data for this column will be
+ * ordered. Four types (string, numeric, date and html (which will strip
+ * HTML tags before ordering)) are currently available. Note that only date
+ * formats understood by Javascript's Date() object will be accepted as type
+ * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string',
+ * 'numeric', 'date' or 'html' (by default). Further types can be adding
+ * through plug-ins.
+ * @type string
+ * @default null Auto-detected from raw data
+ *
+ * @name DataTable.defaults.column.type
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "type": "html", "targets": [ 0 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "type": "html" },
+ * null,
+ * null,
+ * null,
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "sType": null,
+
+
+ /**
+ * Defining the width of the column, this parameter may take any CSS value
+ * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not
+ * been given a specific width through this interface ensuring that the table
+ * remains readable.
+ * @type string
+ * @default null Automatic
+ *
+ * @name DataTable.defaults.column.width
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "width": "20%", "targets": [ 0 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "width": "20%" },
+ * null,
+ * null,
+ * null,
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "sWidth": null
+ };
+
+ _fnHungarianMap( DataTable.defaults.column );
+
+
+
+ /**
+ * DataTables settings object - this holds all the information needed for a
+ * given table, including configuration, data and current application of the
+ * table options. DataTables does not have a single instance for each DataTable
+ * with the settings attached to that instance, but rather instances of the
+ * DataTable "class" are created on-the-fly as needed (typically by a
+ * $().dataTable() call) and the settings object is then applied to that
+ * instance.
+ *
+ * Note that this object is related to {@link DataTable.defaults} but this
+ * one is the internal data store for DataTables's cache of columns. It should
+ * NOT be manipulated outside of DataTables. Any configuration should be done
+ * through the initialisation options.
+ * @namespace
+ * @todo Really should attach the settings object to individual instances so we
+ * don't need to create new instances on each $().dataTable() call (if the
+ * table already exists). It would also save passing oSettings around and
+ * into every single function. However, this is a very significant
+ * architecture change for DataTables and will almost certainly break
+ * backwards compatibility with older installations. This is something that
+ * will be done in 2.0.
+ */
+ DataTable.models.oSettings = {
+ /**
+ * Primary features of DataTables and their enablement state.
+ * @namespace
+ */
+ "oFeatures": {
+
+ /**
+ * Flag to say if DataTables should automatically try to calculate the
+ * optimum table and columns widths (true) or not (false).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bAutoWidth": null,
+
+ /**
+ * Delay the creation of TR and TD elements until they are actually
+ * needed by a driven page draw. This can give a significant speed
+ * increase for Ajax source and Javascript source data, but makes no
+ * difference at all fro DOM and server-side processing tables.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bDeferRender": null,
+
+ /**
+ * Enable filtering on the table or not. Note that if this is disabled
+ * then there is no filtering at all on the table, including fnFilter.
+ * To just remove the filtering input use sDom and remove the 'f' option.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bFilter": null,
+
+ /**
+ * Table information element (the 'Showing x of y records' div) enable
+ * flag.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bInfo": null,
+
+ /**
+ * Present a user control allowing the end user to change the page size
+ * when pagination is enabled.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bLengthChange": null,
+
+ /**
+ * Pagination enabled or not. Note that if this is disabled then length
+ * changing must also be disabled.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bPaginate": null,
+
+ /**
+ * Processing indicator enable flag whenever DataTables is enacting a
+ * user request - typically an Ajax request for server-side processing.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bProcessing": null,
+
+ /**
+ * Server-side processing enabled flag - when enabled DataTables will
+ * get all data from the server for every draw - there is no filtering,
+ * sorting or paging done on the client-side.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bServerSide": null,
+
+ /**
+ * Sorting enablement flag.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bSort": null,
+
+ /**
+ * Multi-column sorting
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bSortMulti": null,
+
+ /**
+ * Apply a class to the columns which are being sorted to provide a
+ * visual highlight or not. This can slow things down when enabled since
+ * there is a lot of DOM interaction.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bSortClasses": null,
+
+ /**
+ * State saving enablement flag.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bStateSave": null
+ },
+
+
+ /**
+ * Scrolling settings for a table.
+ * @namespace
+ */
+ "oScroll": {
+ /**
+ * When the table is shorter in height than sScrollY, collapse the
+ * table container down to the height of the table (when true).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bCollapse": null,
+
+ /**
+ * Width of the scrollbar for the web-browser's platform. Calculated
+ * during table initialisation.
+ * @type int
+ * @default 0
+ */
+ "iBarWidth": 0,
+
+ /**
+ * Viewport width for horizontal scrolling. Horizontal scrolling is
+ * disabled if an empty string.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ */
+ "sX": null,
+
+ /**
+ * Width to expand the table to when using x-scrolling. Typically you
+ * should not need to use this.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ * @deprecated
+ */
+ "sXInner": null,
+
+ /**
+ * Viewport height for vertical scrolling. Vertical scrolling is disabled
+ * if an empty string.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ */
+ "sY": null
+ },
+
+ /**
+ * Language information for the table.
+ * @namespace
+ * @extends DataTable.defaults.oLanguage
+ */
+ "oLanguage": {
+ /**
+ * Information callback function. See
+ * {@link DataTable.defaults.fnInfoCallback}
+ * @type function
+ * @default null
+ */
+ "fnInfoCallback": null
+ },
+
+ /**
+ * Browser support parameters
+ * @namespace
+ */
+ "oBrowser": {
+ /**
+ * Indicate if the browser incorrectly calculates width:100% inside a
+ * scrolling element (IE6/7)
+ * @type boolean
+ * @default false
+ */
+ "bScrollOversize": false,
+
+ /**
+ * Determine if the vertical scrollbar is on the right or left of the
+ * scrolling container - needed for rtl language layout, although not
+ * all browsers move the scrollbar (Safari).
+ * @type boolean
+ * @default false
+ */
+ "bScrollbarLeft": false,
+
+ /**
+ * Flag for if `getBoundingClientRect` is fully supported or not
+ * @type boolean
+ * @default false
+ */
+ "bBounding": false,
+
+ /**
+ * Browser scrollbar width
+ * @type integer
+ * @default 0
+ */
+ "barWidth": 0
+ },
+
+
+ "ajax": null,
+
+
+ /**
+ * Array referencing the nodes which are used for the features. The
+ * parameters of this object match what is allowed by sDom - i.e.
+ *
+ * 'l' - Length changing
+ * 'f' - Filtering input
+ * 't' - The table!
+ * 'i' - Information
+ * 'p' - Pagination
+ * 'r' - pRocessing
+ *
+ * @type array
+ * @default []
+ */
+ "aanFeatures": [],
+
+ /**
+ * Store data information - see {@link DataTable.models.oRow} for detailed
+ * information.
+ * @type array
+ * @default []
+ */
+ "aoData": [],
+
+ /**
+ * Array of indexes which are in the current display (after filtering etc)
+ * @type array
+ * @default []
+ */
+ "aiDisplay": [],
+
+ /**
+ * Array of indexes for display - no filtering
+ * @type array
+ * @default []
+ */
+ "aiDisplayMaster": [],
+
+ /**
+ * Map of row ids to data indexes
+ * @type object
+ * @default {}
+ */
+ "aIds": {},
+
+ /**
+ * Store information about each column that is in use
+ * @type array
+ * @default []
+ */
+ "aoColumns": [],
+
+ /**
+ * Store information about the table's header
+ * @type array
+ * @default []
+ */
+ "aoHeader": [],
+
+ /**
+ * Store information about the table's footer
+ * @type array
+ * @default []
+ */
+ "aoFooter": [],
+
+ /**
+ * Store the applied global search information in case we want to force a
+ * research or compare the old search to a new one.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @namespace
+ * @extends DataTable.models.oSearch
+ */
+ "oPreviousSearch": {},
+
+ /**
+ * Store the applied search for each column - see
+ * {@link DataTable.models.oSearch} for the format that is used for the
+ * filtering information for each column.
+ * @type array
+ * @default []
+ */
+ "aoPreSearchCols": [],
+
+ /**
+ * Sorting that is applied to the table. Note that the inner arrays are
+ * used in the following manner:
+ *
+ * Index 0 - column number
+ * Index 1 - current sorting direction
+ *
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @todo These inner arrays should really be objects
+ */
+ "aaSorting": null,
+
+ /**
+ * Sorting that is always applied to the table (i.e. prefixed in front of
+ * aaSorting).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @default []
+ */
+ "aaSortingFixed": [],
+
+ /**
+ * Classes to use for the striping of a table.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @default []
+ */
+ "asStripeClasses": null,
+
+ /**
+ * If restoring a table - we should restore its striping classes as well
+ * @type array
+ * @default []
+ */
+ "asDestroyStripes": [],
+
+ /**
+ * If restoring a table - we should restore its width
+ * @type int
+ * @default 0
+ */
+ "sDestroyWidth": 0,
+
+ /**
+ * Callback functions array for every time a row is inserted (i.e. on a draw).
+ * @type array
+ * @default []
+ */
+ "aoRowCallback": [],
+
+ /**
+ * Callback functions for the header on each draw.
+ * @type array
+ * @default []
+ */
+ "aoHeaderCallback": [],
+
+ /**
+ * Callback function for the footer on each draw.
+ * @type array
+ * @default []
+ */
+ "aoFooterCallback": [],
+
+ /**
+ * Array of callback functions for draw callback functions
+ * @type array
+ * @default []
+ */
+ "aoDrawCallback": [],
+
+ /**
+ * Array of callback functions for row created function
+ * @type array
+ * @default []
+ */
+ "aoRowCreatedCallback": [],
+
+ /**
+ * Callback functions for just before the table is redrawn. A return of
+ * false will be used to cancel the draw.
+ * @type array
+ * @default []
+ */
+ "aoPreDrawCallback": [],
+
+ /**
+ * Callback functions for when the table has been initialised.
+ * @type array
+ * @default []
+ */
+ "aoInitComplete": [],
+
+
+ /**
+ * Callbacks for modifying the settings to be stored for state saving, prior to
+ * saving state.
+ * @type array
+ * @default []
+ */
+ "aoStateSaveParams": [],
+
+ /**
+ * Callbacks for modifying the settings that have been stored for state saving
+ * prior to using the stored values to restore the state.
+ * @type array
+ * @default []
+ */
+ "aoStateLoadParams": [],
+
+ /**
+ * Callbacks for operating on the settings object once the saved state has been
+ * loaded
+ * @type array
+ * @default []
+ */
+ "aoStateLoaded": [],
+
+ /**
+ * Cache the table ID for quick access
+ * @type string
+ * @default Empty string
+ */
+ "sTableId": "",
+
+ /**
+ * The TABLE node for the main table
+ * @type node
+ * @default null
+ */
+ "nTable": null,
+
+ /**
+ * Permanent ref to the thead element
+ * @type node
+ * @default null
+ */
+ "nTHead": null,
+
+ /**
+ * Permanent ref to the tfoot element - if it exists
+ * @type node
+ * @default null
+ */
+ "nTFoot": null,
+
+ /**
+ * Permanent ref to the tbody element
+ * @type node
+ * @default null
+ */
+ "nTBody": null,
+
+ /**
+ * Cache the wrapper node (contains all DataTables controlled elements)
+ * @type node
+ * @default null
+ */
+ "nTableWrapper": null,
+
+ /**
+ * Indicate if when using server-side processing the loading of data
+ * should be deferred until the second draw.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ * @default false
+ */
+ "bDeferLoading": false,
+
+ /**
+ * Indicate if all required information has been read in
+ * @type boolean
+ * @default false
+ */
+ "bInitialised": false,
+
+ /**
+ * Information about open rows. Each object in the array has the parameters
+ * 'nTr' and 'nParent'
+ * @type array
+ * @default []
+ */
+ "aoOpenRows": [],
+
+ /**
+ * Dictate the positioning of DataTables' control elements - see
+ * {@link DataTable.model.oInit.sDom}.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ * @default null
+ */
+ "sDom": null,
+
+ /**
+ * Search delay (in mS)
+ * @type integer
+ * @default null
+ */
+ "searchDelay": null,
+
+ /**
+ * Which type of pagination should be used.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ * @default two_button
+ */
+ "sPaginationType": "two_button",
+
+ /**
+ * The state duration (for `stateSave`) in seconds.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type int
+ * @default 0
+ */
+ "iStateDuration": 0,
+
+ /**
+ * Array of callback functions for state saving. Each array element is an
+ * object with the following parameters:
+ *
+ * function:fn - function to call. Takes two parameters, oSettings
+ * and the JSON string to save that has been thus far created. Returns
+ * a JSON string to be inserted into a json object
+ * (i.e. '"param": [ 0, 1, 2]')
+ * string:sName - name of callback
+ *
+ * @type array
+ * @default []
+ */
+ "aoStateSave": [],
+
+ /**
+ * Array of callback functions for state loading. Each array element is an
+ * object with the following parameters:
+ *
+ * function:fn - function to call. Takes two parameters, oSettings
+ * and the object stored. May return false to cancel state loading
+ * string:sName - name of callback
+ *
+ * @type array
+ * @default []
+ */
+ "aoStateLoad": [],
+
+ /**
+ * State that was saved. Useful for back reference
+ * @type object
+ * @default null
+ */
+ "oSavedState": null,
+
+ /**
+ * State that was loaded. Useful for back reference
+ * @type object
+ * @default null
+ */
+ "oLoadedState": null,
+
+ /**
+ * Source url for AJAX data for the table.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ * @default null
+ */
+ "sAjaxSource": null,
+
+ /**
+ * Property from a given object from which to read the table data from. This
+ * can be an empty string (when not server-side processing), in which case
+ * it is assumed an an array is given directly.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ */
+ "sAjaxDataProp": null,
+
+ /**
+ * Note if draw should be blocked while getting data
+ * @type boolean
+ * @default true
+ */
+ "bAjaxDataGet": true,
+
+ /**
+ * The last jQuery XHR object that was used for server-side data gathering.
+ * This can be used for working with the XHR information in one of the
+ * callbacks
+ * @type object
+ * @default null
+ */
+ "jqXHR": null,
+
+ /**
+ * JSON returned from the server in the last Ajax request
+ * @type object
+ * @default undefined
+ */
+ "json": undefined,
+
+ /**
+ * Data submitted as part of the last Ajax request
+ * @type object
+ * @default undefined
+ */
+ "oAjaxData": undefined,
+
+ /**
+ * Function to get the server-side data.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type function
+ */
+ "fnServerData": null,
+
+ /**
+ * Functions which are called prior to sending an Ajax request so extra
+ * parameters can easily be sent to the server
+ * @type array
+ * @default []
+ */
+ "aoServerParams": [],
+
+ /**
+ * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if
+ * required).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ */
+ "sServerMethod": null,
+
+ /**
+ * Format numbers for display.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type function
+ */
+ "fnFormatNumber": null,
+
+ /**
+ * List of options that can be used for the user selectable length menu.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @default []
+ */
+ "aLengthMenu": null,
+
+ /**
+ * Counter for the draws that the table does. Also used as a tracker for
+ * server-side processing
+ * @type int
+ * @default 0
+ */
+ "iDraw": 0,
+
+ /**
+ * Indicate if a redraw is being done - useful for Ajax
+ * @type boolean
+ * @default false
+ */
+ "bDrawing": false,
+
+ /**
+ * Draw index (iDraw) of the last error when parsing the returned data
+ * @type int
+ * @default -1
+ */
+ "iDrawError": -1,
+
+ /**
+ * Paging display length
+ * @type int
+ * @default 10
+ */
+ "_iDisplayLength": 10,
+
+ /**
+ * Paging start point - aiDisplay index
+ * @type int
+ * @default 0
+ */
+ "_iDisplayStart": 0,
+
+ /**
+ * Server-side processing - number of records in the result set
+ * (i.e. before filtering), Use fnRecordsTotal rather than
+ * this property to get the value of the number of records, regardless of
+ * the server-side processing setting.
+ * @type int
+ * @default 0
+ * @private
+ */
+ "_iRecordsTotal": 0,
+
+ /**
+ * Server-side processing - number of records in the current display set
+ * (i.e. after filtering). Use fnRecordsDisplay rather than
+ * this property to get the value of the number of records, regardless of
+ * the server-side processing setting.
+ * @type boolean
+ * @default 0
+ * @private
+ */
+ "_iRecordsDisplay": 0,
+
+ /**
+ * Flag to indicate if jQuery UI marking and classes should be used.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bJUI": null,
+
+ /**
+ * The classes to use for the table
+ * @type object
+ * @default {}
+ */
+ "oClasses": {},
+
+ /**
+ * Flag attached to the settings object so you can check in the draw
+ * callback if filtering has been done in the draw. Deprecated in favour of
+ * events.
+ * @type boolean
+ * @default false
+ * @deprecated
+ */
+ "bFiltered": false,
+
+ /**
+ * Flag attached to the settings object so you can check in the draw
+ * callback if sorting has been done in the draw. Deprecated in favour of
+ * events.
+ * @type boolean
+ * @default false
+ * @deprecated
+ */
+ "bSorted": false,
+
+ /**
+ * Indicate that if multiple rows are in the header and there is more than
+ * one unique cell per column, if the top one (true) or bottom one (false)
+ * should be used for sorting / title by DataTables.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bSortCellsTop": null,
+
+ /**
+ * Initialisation object that is used for the table
+ * @type object
+ * @default null
+ */
+ "oInit": null,
+
+ /**
+ * Destroy callback functions - for plug-ins to attach themselves to the
+ * destroy so they can clean up markup and events.
+ * @type array
+ * @default []
+ */
+ "aoDestroyCallback": [],
+
+
+ /**
+ * Get the number of records in the current record set, before filtering
+ * @type function
+ */
+ "fnRecordsTotal": function ()
+ {
+ return _fnDataSource( this ) == 'ssp' ?
+ this._iRecordsTotal * 1 :
+ this.aiDisplayMaster.length;
+ },
+
+ /**
+ * Get the number of records in the current record set, after filtering
+ * @type function
+ */
+ "fnRecordsDisplay": function ()
+ {
+ return _fnDataSource( this ) == 'ssp' ?
+ this._iRecordsDisplay * 1 :
+ this.aiDisplay.length;
+ },
+
+ /**
+ * Get the display end point - aiDisplay index
+ * @type function
+ */
+ "fnDisplayEnd": function ()
+ {
+ var
+ len = this._iDisplayLength,
+ start = this._iDisplayStart,
+ calc = start + len,
+ records = this.aiDisplay.length,
+ features = this.oFeatures,
+ paginate = features.bPaginate;
+
+ if ( features.bServerSide ) {
+ return paginate === false || len === -1 ?
+ start + records :
+ Math.min( start+len, this._iRecordsDisplay );
+ }
+ else {
+ return ! paginate || calc>records || len===-1 ?
+ records :
+ calc;
+ }
+ },
+
+ /**
+ * The DataTables object for this table
+ * @type object
+ * @default null
+ */
+ "oInstance": null,
+
+ /**
+ * Unique identifier for each instance of the DataTables object. If there
+ * is an ID on the table node, then it takes that value, otherwise an
+ * incrementing internal counter is used.
+ * @type string
+ * @default null
+ */
+ "sInstance": null,
+
+ /**
+ * tabindex attribute value that is added to DataTables control elements, allowing
+ * keyboard navigation of the table and its controls.
+ */
+ "iTabIndex": 0,
+
+ /**
+ * DIV container for the footer scrolling table if scrolling
+ */
+ "nScrollHead": null,
+
+ /**
+ * DIV container for the footer scrolling table if scrolling
+ */
+ "nScrollFoot": null,
+
+ /**
+ * Last applied sort
+ * @type array
+ * @default []
+ */
+ "aLastSort": [],
+
+ /**
+ * Stored plug-in instances
+ * @type object
+ * @default {}
+ */
+ "oPlugins": {},
+
+ /**
+ * Function used to get a row's id from the row's data
+ * @type function
+ * @default null
+ */
+ "rowIdFn": null,
+
+ /**
+ * Data location where to store a row's id
+ * @type string
+ * @default null
+ */
+ "rowId": null
+ };
+
+ /**
+ * Extension object for DataTables that is used to provide all extension
+ * options.
+ *
+ * Note that the `DataTable.ext` object is available through
+ * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is
+ * also aliased to `jQuery.fn.dataTableExt` for historic reasons.
+ * @namespace
+ * @extends DataTable.models.ext
+ */
+
+
+ /**
+ * DataTables extensions
+ *
+ * This namespace acts as a collection area for plug-ins that can be used to
+ * extend DataTables capabilities. Indeed many of the build in methods
+ * use this method to provide their own capabilities (sorting methods for
+ * example).
+ *
+ * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy
+ * reasons
+ *
+ * @namespace
+ */
+ DataTable.ext = _ext = {
+ /**
+ * Buttons. For use with the Buttons extension for DataTables. This is
+ * defined here so other extensions can define buttons regardless of load
+ * order. It is _not_ used by DataTables core.
+ *
+ * @type object
+ * @default {}
+ */
+ buttons: {},
+
+
+ /**
+ * Element class names
+ *
+ * @type object
+ * @default {}
+ */
+ classes: {},
+
+
+ /**
+ * Error reporting.
+ *
+ * How should DataTables report an error. Can take the value 'alert',
+ * 'throw', 'none' or a function.
+ *
+ * @type string|function
+ * @default alert
+ */
+ errMode: "alert",
+
+
+ /**
+ * Feature plug-ins.
+ *
+ * This is an array of objects which describe the feature plug-ins that are
+ * available to DataTables. These feature plug-ins are then available for
+ * use through the `dom` initialisation option.
+ *
+ * Each feature plug-in is described by an object which must have the
+ * following properties:
+ *
+ * * `fnInit` - function that is used to initialise the plug-in,
+ * * `cFeature` - a character so the feature can be enabled by the `dom`
+ * instillation option. This is case sensitive.
+ *
+ * The `fnInit` function has the following input parameters:
+ *
+ * 1. `{object}` DataTables settings object: see
+ * {@link DataTable.models.oSettings}
+ *
+ * And the following return is expected:
+ *
+ * * {node|null} The element which contains your feature. Note that the
+ * return may also be void if your plug-in does not require to inject any
+ * DOM elements into DataTables control (`dom`) - for example this might
+ * be useful when developing a plug-in which allows table control via
+ * keyboard entry
+ *
+ * @type array
+ *
+ * @example
+ * $.fn.dataTable.ext.features.push( {
+ * "fnInit": function( oSettings ) {
+ * return new TableTools( { "oDTSettings": oSettings } );
+ * },
+ * "cFeature": "T"
+ * } );
+ */
+ feature: [],
+
+
+ /**
+ * Row searching.
+ *
+ * This method of searching is complimentary to the default type based
+ * searching, and a lot more comprehensive as it allows you complete control
+ * over the searching logic. Each element in this array is a function
+ * (parameters described below) that is called for every row in the table,
+ * and your logic decides if it should be included in the searching data set
+ * or not.
+ *
+ * Searching functions have the following input parameters:
+ *
+ * 1. `{object}` DataTables settings object: see
+ * {@link DataTable.models.oSettings}
+ * 2. `{array|object}` Data for the row to be processed (same as the
+ * original format that was passed in as the data source, or an array
+ * from a DOM data source
+ * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which
+ * can be useful to retrieve the `TR` element if you need DOM interaction.
+ *
+ * And the following return is expected:
+ *
+ * * {boolean} Include the row in the searched result set (true) or not
+ * (false)
+ *
+ * Note that as with the main search ability in DataTables, technically this
+ * is "filtering", since it is subtractive. However, for consistency in
+ * naming we call it searching here.
+ *
+ * @type array
+ * @default []
+ *
+ * @example
+ * // The following example shows custom search being applied to the
+ * // fourth column (i.e. the data[3] index) based on two input values
+ * // from the end-user, matching the data in a certain range.
+ * $.fn.dataTable.ext.search.push(
+ * function( settings, data, dataIndex ) {
+ * var min = document.getElementById('min').value * 1;
+ * var max = document.getElementById('max').value * 1;
+ * var version = data[3] == "-" ? 0 : data[3]*1;
+ *
+ * if ( min == "" && max == "" ) {
+ * return true;
+ * }
+ * else if ( min == "" && version < max ) {
+ * return true;
+ * }
+ * else if ( min < version && "" == max ) {
+ * return true;
+ * }
+ * else if ( min < version && version < max ) {
+ * return true;
+ * }
+ * return false;
+ * }
+ * );
+ */
+ search: [],
+
+
+ /**
+ * Selector extensions
+ *
+ * The `selector` option can be used to extend the options available for the
+ * selector modifier options (`selector-modifier` object data type) that
+ * each of the three built in selector types offer (row, column and cell +
+ * their plural counterparts). For example the Select extension uses this
+ * mechanism to provide an option to select only rows, columns and cells
+ * that have been marked as selected by the end user (`{selected: true}`),
+ * which can be used in conjunction with the existing built in selector
+ * options.
+ *
+ * Each property is an array to which functions can be pushed. The functions
+ * take three attributes:
+ *
+ * * Settings object for the host table
+ * * Options object (`selector-modifier` object type)
+ * * Array of selected item indexes
+ *
+ * The return is an array of the resulting item indexes after the custom
+ * selector has been applied.
+ *
+ * @type object
+ */
+ selector: {
+ cell: [],
+ column: [],
+ row: []
+ },
+
+
+ /**
+ * Internal functions, exposed for used in plug-ins.
+ *
+ * Please note that you should not need to use the internal methods for
+ * anything other than a plug-in (and even then, try to avoid if possible).
+ * The internal function may change between releases.
+ *
+ * @type object
+ * @default {}
+ */
+ internal: {},
+
+
+ /**
+ * Legacy configuration options. Enable and disable legacy options that
+ * are available in DataTables.
+ *
+ * @type object
+ */
+ legacy: {
+ /**
+ * Enable / disable DataTables 1.9 compatible server-side processing
+ * requests
+ *
+ * @type boolean
+ * @default null
+ */
+ ajax: null
+ },
+
+
+ /**
+ * Pagination plug-in methods.
+ *
+ * Each entry in this object is a function and defines which buttons should
+ * be shown by the pagination rendering method that is used for the table:
+ * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the
+ * buttons are displayed in the document, while the functions here tell it
+ * what buttons to display. This is done by returning an array of button
+ * descriptions (what each button will do).
+ *
+ * Pagination types (the four built in options and any additional plug-in
+ * options defined here) can be used through the `paginationType`
+ * initialisation parameter.
+ *
+ * The functions defined take two parameters:
+ *
+ * 1. `{int} page` The current page index
+ * 2. `{int} pages` The number of pages in the table
+ *
+ * Each function is expected to return an array where each element of the
+ * array can be one of:
+ *
+ * * `first` - Jump to first page when activated
+ * * `last` - Jump to last page when activated
+ * * `previous` - Show previous page when activated
+ * * `next` - Show next page when activated
+ * * `{int}` - Show page of the index given
+ * * `{array}` - A nested array containing the above elements to add a
+ * containing 'DIV' element (might be useful for styling).
+ *
+ * Note that DataTables v1.9- used this object slightly differently whereby
+ * an object with two functions would be defined for each plug-in. That
+ * ability is still supported by DataTables 1.10+ to provide backwards
+ * compatibility, but this option of use is now decremented and no longer
+ * documented in DataTables 1.10+.
+ *
+ * @type object
+ * @default {}
+ *
+ * @example
+ * // Show previous, next and current page buttons only
+ * $.fn.dataTableExt.oPagination.current = function ( page, pages ) {
+ * return [ 'previous', page, 'next' ];
+ * };
+ */
+ pager: {},
+
+
+ renderer: {
+ pageButton: {},
+ header: {}
+ },
+
+
+ /**
+ * Ordering plug-ins - custom data source
+ *
+ * The extension options for ordering of data available here is complimentary
+ * to the default type based ordering that DataTables typically uses. It
+ * allows much greater control over the the data that is being used to
+ * order a column, but is necessarily therefore more complex.
+ *
+ * This type of ordering is useful if you want to do ordering based on data
+ * live from the DOM (for example the contents of an 'input' element) rather
+ * than just the static string that DataTables knows of.
+ *
+ * The way these plug-ins work is that you create an array of the values you
+ * wish to be ordering for the column in question and then return that
+ * array. The data in the array much be in the index order of the rows in
+ * the table (not the currently ordering order!). Which order data gathering
+ * function is run here depends on the `dt-init columns.orderDataType`
+ * parameter that is used for the column (if any).
+ *
+ * The functions defined take two parameters:
+ *
+ * 1. `{object}` DataTables settings object: see
+ * {@link DataTable.models.oSettings}
+ * 2. `{int}` Target column index
+ *
+ * Each function is expected to return an array:
+ *
+ * * `{array}` Data for the column to be ordering upon
+ *
+ * @type array
+ *
+ * @example
+ * // Ordering using `input` node values
+ * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col )
+ * {
+ * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) {
+ * return $('input', td).val();
+ * } );
+ * }
+ */
+ order: {},
+
+
+ /**
+ * Type based plug-ins.
+ *
+ * Each column in DataTables has a type assigned to it, either by automatic
+ * detection or by direct assignment using the `type` option for the column.
+ * The type of a column will effect how it is ordering and search (plug-ins
+ * can also make use of the column type if required).
+ *
+ * @namespace
+ */
+ type: {
+ /**
+ * Type detection functions.
+ *
+ * The functions defined in this object are used to automatically detect
+ * a column's type, making initialisation of DataTables super easy, even
+ * when complex data is in the table.
+ *
+ * The functions defined take two parameters:
+ *
+ * 1. `{*}` Data from the column cell to be analysed
+ * 2. `{settings}` DataTables settings object. This can be used to
+ * perform context specific type detection - for example detection
+ * based on language settings such as using a comma for a decimal
+ * place. Generally speaking the options from the settings will not
+ * be required
+ *
+ * Each function is expected to return:
+ *
+ * * `{string|null}` Data type detected, or null if unknown (and thus
+ * pass it on to the other type detection functions.
+ *
+ * @type array
+ *
+ * @example
+ * // Currency type detection plug-in:
+ * $.fn.dataTable.ext.type.detect.push(
+ * function ( data, settings ) {
+ * // Check the numeric part
+ * if ( ! $.isNumeric( data.substring(1) ) ) {
+ * return null;
+ * }
+ *
+ * // Check prefixed by currency
+ * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) {
+ * return 'currency';
+ * }
+ * return null;
+ * }
+ * );
+ */
+ detect: [],
+
+
+ /**
+ * Type based search formatting.
+ *
+ * The type based searching functions can be used to pre-format the
+ * data to be search on. For example, it can be used to strip HTML
+ * tags or to de-format telephone numbers for numeric only searching.
+ *
+ * Note that is a search is not defined for a column of a given type,
+ * no search formatting will be performed.
+ *
+ * Pre-processing of searching data plug-ins - When you assign the sType
+ * for a column (or have it automatically detected for you by DataTables
+ * or a type detection plug-in), you will typically be using this for
+ * custom sorting, but it can also be used to provide custom searching
+ * by allowing you to pre-processing the data and returning the data in
+ * the format that should be searched upon. This is done by adding
+ * functions this object with a parameter name which matches the sType
+ * for that target column. This is the corollary of afnSortData
+ * for searching data.
+ *
+ * The functions defined take a single parameter:
+ *
+ * 1. `{*}` Data from the column cell to be prepared for searching
+ *
+ * Each function is expected to return:
+ *
+ * * `{string|null}` Formatted string that will be used for the searching.
+ *
+ * @type object
+ * @default {}
+ *
+ * @example
+ * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) {
+ * return d.replace(/\n/g," ").replace( /<.*?>/g, "" );
+ * }
+ */
+ search: {},
+
+
+ /**
+ * Type based ordering.
+ *
+ * The column type tells DataTables what ordering to apply to the table
+ * when a column is sorted upon. The order for each type that is defined,
+ * is defined by the functions available in this object.
+ *
+ * Each ordering option can be described by three properties added to
+ * this object:
+ *
+ * * `{type}-pre` - Pre-formatting function
+ * * `{type}-asc` - Ascending order function
+ * * `{type}-desc` - Descending order function
+ *
+ * All three can be used together, only `{type}-pre` or only
+ * `{type}-asc` and `{type}-desc` together. It is generally recommended
+ * that only `{type}-pre` is used, as this provides the optimal
+ * implementation in terms of speed, although the others are provided
+ * for compatibility with existing Javascript sort functions.
+ *
+ * `{type}-pre`: Functions defined take a single parameter:
+ *
+ * 1. `{*}` Data from the column cell to be prepared for ordering
+ *
+ * And return:
+ *
+ * * `{*}` Data to be sorted upon
+ *
+ * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort
+ * functions, taking two parameters:
+ *
+ * 1. `{*}` Data to compare to the second parameter
+ * 2. `{*}` Data to compare to the first parameter
+ *
+ * And returning:
+ *
+ * * `{*}` Ordering match: <0 if first parameter should be sorted lower
+ * than the second parameter, ===0 if the two parameters are equal and
+ * >0 if the first parameter should be sorted height than the second
+ * parameter.
+ *
+ * @type object
+ * @default {}
+ *
+ * @example
+ * // Numeric ordering of formatted numbers with a pre-formatter
+ * $.extend( $.fn.dataTable.ext.type.order, {
+ * "string-pre": function(x) {
+ * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" );
+ * return parseFloat( a );
+ * }
+ * } );
+ *
+ * @example
+ * // Case-sensitive string ordering, with no pre-formatting method
+ * $.extend( $.fn.dataTable.ext.order, {
+ * "string-case-asc": function(x,y) {
+ * return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+ * },
+ * "string-case-desc": function(x,y) {
+ * return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+ * }
+ * } );
+ */
+ order: {}
+ },
+
+ /**
+ * Unique DataTables instance counter
+ *
+ * @type int
+ * @private
+ */
+ _unique: 0,
+
+
+ //
+ // Depreciated
+ // The following properties are retained for backwards compatiblity only.
+ // The should not be used in new projects and will be removed in a future
+ // version
+ //
+
+ /**
+ * Version check function.
+ * @type function
+ * @depreciated Since 1.10
+ */
+ fnVersionCheck: DataTable.fnVersionCheck,
+
+
+ /**
+ * Index for what 'this' index API functions should use
+ * @type int
+ * @deprecated Since v1.10
+ */
+ iApiIndex: 0,
+
+
+ /**
+ * jQuery UI class container
+ * @type object
+ * @deprecated Since v1.10
+ */
+ oJUIClasses: {},
+
+
+ /**
+ * Software version
+ * @type string
+ * @deprecated Since v1.10
+ */
+ sVersion: DataTable.version
+ };
+
+
+ //
+ // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
+ //
+ $.extend( _ext, {
+ afnFiltering: _ext.search,
+ aTypes: _ext.type.detect,
+ ofnSearch: _ext.type.search,
+ oSort: _ext.type.order,
+ afnSortData: _ext.order,
+ aoFeatures: _ext.feature,
+ oApi: _ext.internal,
+ oStdClasses: _ext.classes,
+ oPagination: _ext.pager
+ } );
+
+
+ $.extend( DataTable.ext.classes, {
+ "sTable": "dataTable",
+ "sNoFooter": "no-footer",
+
+ /* Paging buttons */
+ "sPageButton": "paginate_button",
+ "sPageButtonActive": "current",
+ "sPageButtonDisabled": "disabled",
+
+ /* Striping classes */
+ "sStripeOdd": "odd",
+ "sStripeEven": "even",
+
+ /* Empty row */
+ "sRowEmpty": "dataTables_empty",
+
+ /* Features */
+ "sWrapper": "dataTables_wrapper",
+ "sFilter": "dataTables_filter",
+ "sInfo": "dataTables_info",
+ "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
+ "sLength": "dataTables_length",
+ "sProcessing": "dataTables_processing",
+
+ /* Sorting */
+ "sSortAsc": "sorting_asc",
+ "sSortDesc": "sorting_desc",
+ "sSortable": "sorting", /* Sortable in both directions */
+ "sSortableAsc": "sorting_asc_disabled",
+ "sSortableDesc": "sorting_desc_disabled",
+ "sSortableNone": "sorting_disabled",
+ "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
+
+ /* Filtering */
+ "sFilterInput": "",
+
+ /* Page length */
+ "sLengthSelect": "",
+
+ /* Scrolling */
+ "sScrollWrapper": "dataTables_scroll",
+ "sScrollHead": "dataTables_scrollHead",
+ "sScrollHeadInner": "dataTables_scrollHeadInner",
+ "sScrollBody": "dataTables_scrollBody",
+ "sScrollFoot": "dataTables_scrollFoot",
+ "sScrollFootInner": "dataTables_scrollFootInner",
+
+ /* Misc */
+ "sHeaderTH": "",
+ "sFooterTH": "",
+
+ // Deprecated
+ "sSortJUIAsc": "",
+ "sSortJUIDesc": "",
+ "sSortJUI": "",
+ "sSortJUIAscAllowed": "",
+ "sSortJUIDescAllowed": "",
+ "sSortJUIWrapper": "",
+ "sSortIcon": "",
+ "sJUIHeader": "",
+ "sJUIFooter": ""
+ } );
+
+
+ (function() {
+
+ // Reused strings for better compression. Closure compiler appears to have a
+ // weird edge case where it is trying to expand strings rather than use the
+ // variable version. This results in about 200 bytes being added, for very
+ // little preference benefit since it this run on script load only.
+ var _empty = '';
+ _empty = '';
+
+ var _stateDefault = _empty + 'ui-state-default';
+ var _sortIcon = _empty + 'css_right ui-icon ui-icon-';
+ var _headerFooter = _empty + 'fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix';
+
+ $.extend( DataTable.ext.oJUIClasses, DataTable.ext.classes, {
+ /* Full numbers paging buttons */
+ "sPageButton": "fg-button ui-button "+_stateDefault,
+ "sPageButtonActive": "ui-state-disabled",
+ "sPageButtonDisabled": "ui-state-disabled",
+
+ /* Features */
+ "sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi "+
+ "ui-buttonset-multi paging_", /* Note that the type is postfixed */
+
+ /* Sorting */
+ "sSortAsc": _stateDefault+" sorting_asc",
+ "sSortDesc": _stateDefault+" sorting_desc",
+ "sSortable": _stateDefault+" sorting",
+ "sSortableAsc": _stateDefault+" sorting_asc_disabled",
+ "sSortableDesc": _stateDefault+" sorting_desc_disabled",
+ "sSortableNone": _stateDefault+" sorting_disabled",
+ "sSortJUIAsc": _sortIcon+"triangle-1-n",
+ "sSortJUIDesc": _sortIcon+"triangle-1-s",
+ "sSortJUI": _sortIcon+"carat-2-n-s",
+ "sSortJUIAscAllowed": _sortIcon+"carat-1-n",
+ "sSortJUIDescAllowed": _sortIcon+"carat-1-s",
+ "sSortJUIWrapper": "DataTables_sort_wrapper",
+ "sSortIcon": "DataTables_sort_icon",
+
+ /* Scrolling */
+ "sScrollHead": "dataTables_scrollHead "+_stateDefault,
+ "sScrollFoot": "dataTables_scrollFoot "+_stateDefault,
+
+ /* Misc */
+ "sHeaderTH": _stateDefault,
+ "sFooterTH": _stateDefault,
+ "sJUIHeader": _headerFooter+" ui-corner-tl ui-corner-tr",
+ "sJUIFooter": _headerFooter+" ui-corner-bl ui-corner-br"
+ } );
+
+ }());
+
+
+
+ var extPagination = DataTable.ext.pager;
+
+ function _numbers ( page, pages ) {
+ var
+ numbers = [],
+ buttons = extPagination.numbers_length,
+ half = Math.floor( buttons / 2 ),
+ i = 1;
+
+ if ( pages <= buttons ) {
+ numbers = _range( 0, pages );
+ }
+ else if ( page <= half ) {
+ numbers = _range( 0, buttons-2 );
+ numbers.push( 'ellipsis' );
+ numbers.push( pages-1 );
+ }
+ else if ( page >= pages - 1 - half ) {
+ numbers = _range( pages-(buttons-2), pages );
+ numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6
+ numbers.splice( 0, 0, 0 );
+ }
+ else {
+ numbers = _range( page-half+2, page+half-1 );
+ numbers.push( 'ellipsis' );
+ numbers.push( pages-1 );
+ numbers.splice( 0, 0, 'ellipsis' );
+ numbers.splice( 0, 0, 0 );
+ }
+
+ numbers.DT_el = 'span';
+ return numbers;
+ }
+
+
+ $.extend( extPagination, {
+ simple: function ( page, pages ) {
+ return [ 'previous', 'next' ];
+ },
+
+ full: function ( page, pages ) {
+ return [ 'first', 'previous', 'next', 'last' ];
+ },
+
+ numbers: function ( page, pages ) {
+ return [ _numbers(page, pages) ];
+ },
+
+ simple_numbers: function ( page, pages ) {
+ return [ 'previous', _numbers(page, pages), 'next' ];
+ },
+
+ full_numbers: function ( page, pages ) {
+ return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ];
+ },
+
+ // For testing and plug-ins to use
+ _numbers: _numbers,
+
+ // Number of number buttons (including ellipsis) to show. _Must be odd!_
+ numbers_length: 7
+ } );
+
+
+ $.extend( true, DataTable.ext.renderer, {
+ pageButton: {
+ _: function ( settings, host, idx, buttons, page, pages ) {
+ var classes = settings.oClasses;
+ var lang = settings.oLanguage.oPaginate;
+ var btnDisplay, btnClass, counter=0;
+
+ var attach = function( container, buttons ) {
+ var i, ien, node, button;
+ var clickHandler = function ( e ) {
+ _fnPageChange( settings, e.data.action, true );
+ };
+
+ for ( i=0, ien=buttons.length ; i ' )
+ .appendTo( container );
+ attach( inner, button );
+ }
+ else {
+ btnDisplay = null;
+ btnClass = '';
+
+ switch ( button ) {
+ case 'ellipsis':
+ container.append('… ');
+ break;
+
+ case 'first':
+ btnDisplay = lang.sFirst;
+ btnClass = button + (page > 0 ?
+ '' : ' '+classes.sPageButtonDisabled);
+ break;
+
+ case 'previous':
+ btnDisplay = lang.sPrevious;
+ btnClass = button + (page > 0 ?
+ '' : ' '+classes.sPageButtonDisabled);
+ break;
+
+ case 'next':
+ btnDisplay = lang.sNext;
+ btnClass = button + (page < pages-1 ?
+ '' : ' '+classes.sPageButtonDisabled);
+ break;
+
+ case 'last':
+ btnDisplay = lang.sLast;
+ btnClass = button + (page < pages-1 ?
+ '' : ' '+classes.sPageButtonDisabled);
+ break;
+
+ default:
+ btnDisplay = button + 1;
+ btnClass = page === button ?
+ classes.sPageButtonActive : '';
+ break;
+ }
+
+ if ( btnDisplay !== null ) {
+ node = $('', {
+ 'class': classes.sPageButton+' '+btnClass,
+ 'aria-controls': settings.sTableId,
+ 'data-dt-idx': counter,
+ 'tabindex': settings.iTabIndex,
+ 'id': idx === 0 && typeof button === 'string' ?
+ settings.sTableId +'_'+ button :
+ null
+ } )
+ .html( btnDisplay )
+ .appendTo( container );
+
+ _fnBindAction(
+ node, {action: button}, clickHandler
+ );
+
+ counter++;
+ }
+ }
+ }
+ };
+
+ // IE9 throws an 'unknown error' if document.activeElement is used
+ // inside an iframe or frame. Try / catch the error. Not good for
+ // accessibility, but neither are frames.
+ var activeEl;
+
+ try {
+ // Because this approach is destroying and recreating the paging
+ // elements, focus is lost on the select button which is bad for
+ // accessibility. So we want to restore focus once the draw has
+ // completed
+ activeEl = $(host).find(document.activeElement).data('dt-idx');
+ }
+ catch (e) {}
+
+ attach( $(host).empty(), buttons );
+
+ if ( activeEl ) {
+ $(host).find( '[data-dt-idx='+activeEl+']' ).focus();
+ }
+ }
+ }
+ } );
+
+
+
+ // Built in type detection. See model.ext.aTypes for information about
+ // what is required from this methods.
+ $.extend( DataTable.ext.type.detect, [
+ // Plain numbers - first since V8 detects some plain numbers as dates
+ // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...).
+ function ( d, settings )
+ {
+ var decimal = settings.oLanguage.sDecimal;
+ return _isNumber( d, decimal ) ? 'num'+decimal : null;
+ },
+
+ // Dates (only those recognised by the browser's Date.parse)
+ function ( d, settings )
+ {
+ // V8 will remove any unknown characters at the start and end of the
+ // expression, leading to false matches such as `$245.12` or `10%` being
+ // a valid date. See forum thread 18941 for detail.
+ if ( d && !(d instanceof Date) && ( ! _re_date_start.test(d) || ! _re_date_end.test(d) ) ) {
+ return null;
+ }
+ var parsed = Date.parse(d);
+ return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null;
+ },
+
+ // Formatted numbers
+ function ( d, settings )
+ {
+ var decimal = settings.oLanguage.sDecimal;
+ return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null;
+ },
+
+ // HTML numeric
+ function ( d, settings )
+ {
+ var decimal = settings.oLanguage.sDecimal;
+ return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null;
+ },
+
+ // HTML numeric, formatted
+ function ( d, settings )
+ {
+ var decimal = settings.oLanguage.sDecimal;
+ return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null;
+ },
+
+ // HTML (this is strict checking - there must be html)
+ function ( d, settings )
+ {
+ return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ?
+ 'html' : null;
+ }
+ ] );
+
+
+
+ // Filter formatting functions. See model.ext.ofnSearch for information about
+ // what is required from these methods.
+ //
+ // Note that additional search methods are added for the html numbers and
+ // html formatted numbers by `_addNumericSort()` when we know what the decimal
+ // place is
+
+
+ $.extend( DataTable.ext.type.search, {
+ html: function ( data ) {
+ return _empty(data) ?
+ data :
+ typeof data === 'string' ?
+ data
+ .replace( _re_new_lines, " " )
+ .replace( _re_html, "" ) :
+ '';
+ },
+
+ string: function ( data ) {
+ return _empty(data) ?
+ data :
+ typeof data === 'string' ?
+ data.replace( _re_new_lines, " " ) :
+ data;
+ }
+ } );
+
+
+
+ var __numericReplace = function ( d, decimalPlace, re1, re2 ) {
+ if ( d !== 0 && (!d || d === '-') ) {
+ return -Infinity;
+ }
+
+ // If a decimal place other than `.` is used, it needs to be given to the
+ // function so we can detect it and replace with a `.` which is the only
+ // decimal place Javascript recognises - it is not locale aware.
+ if ( decimalPlace ) {
+ d = _numToDecimal( d, decimalPlace );
+ }
+
+ if ( d.replace ) {
+ if ( re1 ) {
+ d = d.replace( re1, '' );
+ }
+
+ if ( re2 ) {
+ d = d.replace( re2, '' );
+ }
+ }
+
+ return d * 1;
+ };
+
+
+ // Add the numeric 'deformatting' functions for sorting and search. This is done
+ // in a function to provide an easy ability for the language options to add
+ // additional methods if a non-period decimal place is used.
+ function _addNumericSort ( decimalPlace ) {
+ $.each(
+ {
+ // Plain numbers
+ "num": function ( d ) {
+ return __numericReplace( d, decimalPlace );
+ },
+
+ // Formatted numbers
+ "num-fmt": function ( d ) {
+ return __numericReplace( d, decimalPlace, _re_formatted_numeric );
+ },
+
+ // HTML numeric
+ "html-num": function ( d ) {
+ return __numericReplace( d, decimalPlace, _re_html );
+ },
+
+ // HTML numeric, formatted
+ "html-num-fmt": function ( d ) {
+ return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric );
+ }
+ },
+ function ( key, fn ) {
+ // Add the ordering method
+ _ext.type.order[ key+decimalPlace+'-pre' ] = fn;
+
+ // For HTML types add a search formatter that will strip the HTML
+ if ( key.match(/^html\-/) ) {
+ _ext.type.search[ key+decimalPlace ] = _ext.type.search.html;
+ }
+ }
+ );
+ }
+
+
+ // Default sort methods
+ $.extend( _ext.type.order, {
+ // Dates
+ "date-pre": function ( d ) {
+ return Date.parse( d ) || 0;
+ },
+
+ // html
+ "html-pre": function ( a ) {
+ return _empty(a) ?
+ '' :
+ a.replace ?
+ a.replace( /<.*?>/g, "" ).toLowerCase() :
+ a+'';
+ },
+
+ // string
+ "string-pre": function ( a ) {
+ // This is a little complex, but faster than always calling toString,
+ // http://jsperf.com/tostring-v-check
+ return _empty(a) ?
+ '' :
+ typeof a === 'string' ?
+ a.toLowerCase() :
+ ! a.toString ?
+ '' :
+ a.toString();
+ },
+
+ // string-asc and -desc are retained only for compatibility with the old
+ // sort methods
+ "string-asc": function ( x, y ) {
+ return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+ },
+
+ "string-desc": function ( x, y ) {
+ return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+ }
+ } );
+
+
+ // Numeric sorting types - order doesn't matter here
+ _addNumericSort( '' );
+
+
+ $.extend( true, DataTable.ext.renderer, {
+ header: {
+ _: function ( settings, cell, column, classes ) {
+ // No additional mark-up required
+ // Attach a sort listener to update on sort - note that using the
+ // `DT` namespace will allow the event to be removed automatically
+ // on destroy, while the `dt` namespaced event is the one we are
+ // listening for
+ $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
+ if ( settings !== ctx ) { // need to check this this is the host
+ return; // table, not a nested one
+ }
+
+ var colIdx = column.idx;
+
+ cell
+ .removeClass(
+ column.sSortingClass +' '+
+ classes.sSortAsc +' '+
+ classes.sSortDesc
+ )
+ .addClass( columns[ colIdx ] == 'asc' ?
+ classes.sSortAsc : columns[ colIdx ] == 'desc' ?
+ classes.sSortDesc :
+ column.sSortingClass
+ );
+ } );
+ },
+
+ jqueryui: function ( settings, cell, column, classes ) {
+ $('
')
+ .addClass( classes.sSortJUIWrapper )
+ .append( cell.contents() )
+ .append( $(' ')
+ .addClass( classes.sSortIcon+' '+column.sSortingClassJUI )
+ )
+ .appendTo( cell );
+
+ // Attach a sort listener to update on sort
+ $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
+ if ( settings !== ctx ) {
+ return;
+ }
+
+ var colIdx = column.idx;
+
+ cell
+ .removeClass( classes.sSortAsc +" "+classes.sSortDesc )
+ .addClass( columns[ colIdx ] == 'asc' ?
+ classes.sSortAsc : columns[ colIdx ] == 'desc' ?
+ classes.sSortDesc :
+ column.sSortingClass
+ );
+
+ cell
+ .find( 'span.'+classes.sSortIcon )
+ .removeClass(
+ classes.sSortJUIAsc +" "+
+ classes.sSortJUIDesc +" "+
+ classes.sSortJUI +" "+
+ classes.sSortJUIAscAllowed +" "+
+ classes.sSortJUIDescAllowed
+ )
+ .addClass( columns[ colIdx ] == 'asc' ?
+ classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ?
+ classes.sSortJUIDesc :
+ column.sSortingClassJUI
+ );
+ } );
+ }
+ }
+ } );
+
+ /*
+ * Public helper functions. These aren't used internally by DataTables, or
+ * called by any of the options passed into DataTables, but they can be used
+ * externally by developers working with DataTables. They are helper functions
+ * to make working with DataTables a little bit easier.
+ */
+
+ /**
+ * Helpers for `columns.render`.
+ *
+ * The options defined here can be used with the `columns.render` initialisation
+ * option to provide a display renderer. The following functions are defined:
+ *
+ * * `number` - Will format numeric data (defined by `columns.data`) for
+ * display, retaining the original unformatted data for sorting and filtering.
+ * It takes 5 parameters:
+ * * `string` - Thousands grouping separator
+ * * `string` - Decimal point indicator
+ * * `integer` - Number of decimal points to show
+ * * `string` (optional) - Prefix.
+ * * `string` (optional) - Postfix (/suffix).
+ *
+ * @example
+ * // Column definition using the number renderer
+ * {
+ * data: "salary",
+ * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' )
+ * }
+ *
+ * @namespace
+ */
+ DataTable.render = {
+ number: function ( thousands, decimal, precision, prefix, postfix ) {
+ return {
+ display: function ( d ) {
+ if ( typeof d !== 'number' && typeof d !== 'string' ) {
+ return d;
+ }
+
+ var negative = d < 0 ? '-' : '';
+ d = Math.abs( parseFloat( d ) );
+
+ var intPart = parseInt( d, 10 );
+ var floatPart = precision ?
+ decimal+(d - intPart).toFixed( precision ).substring( 2 ):
+ '';
+
+ return negative + (prefix||'') +
+ intPart.toString().replace(
+ /\B(?=(\d{3})+(?!\d))/g, thousands
+ ) +
+ floatPart +
+ (postfix||'');
+ }
+ };
+ }
+ };
+
+
+ /*
+ * This is really a good bit rubbish this method of exposing the internal methods
+ * publicly... - To be fixed in 2.0 using methods on the prototype
+ */
+
+
+ /**
+ * Create a wrapper function for exporting an internal functions to an external API.
+ * @param {string} fn API function name
+ * @returns {function} wrapped function
+ * @memberof DataTable#internal
+ */
+ function _fnExternApiFunc (fn)
+ {
+ return function() {
+ var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat(
+ Array.prototype.slice.call(arguments)
+ );
+ return DataTable.ext.internal[fn].apply( this, args );
+ };
+ }
+
+
+ /**
+ * Reference to internal functions for use by plug-in developers. Note that
+ * these methods are references to internal functions and are considered to be
+ * private. If you use these methods, be aware that they are liable to change
+ * between versions.
+ * @namespace
+ */
+ $.extend( DataTable.ext.internal, {
+ _fnExternApiFunc: _fnExternApiFunc,
+ _fnBuildAjax: _fnBuildAjax,
+ _fnAjaxUpdate: _fnAjaxUpdate,
+ _fnAjaxParameters: _fnAjaxParameters,
+ _fnAjaxUpdateDraw: _fnAjaxUpdateDraw,
+ _fnAjaxDataSrc: _fnAjaxDataSrc,
+ _fnAddColumn: _fnAddColumn,
+ _fnColumnOptions: _fnColumnOptions,
+ _fnAdjustColumnSizing: _fnAdjustColumnSizing,
+ _fnVisibleToColumnIndex: _fnVisibleToColumnIndex,
+ _fnColumnIndexToVisible: _fnColumnIndexToVisible,
+ _fnVisbleColumns: _fnVisbleColumns,
+ _fnGetColumns: _fnGetColumns,
+ _fnColumnTypes: _fnColumnTypes,
+ _fnApplyColumnDefs: _fnApplyColumnDefs,
+ _fnHungarianMap: _fnHungarianMap,
+ _fnCamelToHungarian: _fnCamelToHungarian,
+ _fnLanguageCompat: _fnLanguageCompat,
+ _fnBrowserDetect: _fnBrowserDetect,
+ _fnAddData: _fnAddData,
+ _fnAddTr: _fnAddTr,
+ _fnNodeToDataIndex: _fnNodeToDataIndex,
+ _fnNodeToColumnIndex: _fnNodeToColumnIndex,
+ _fnGetCellData: _fnGetCellData,
+ _fnSetCellData: _fnSetCellData,
+ _fnSplitObjNotation: _fnSplitObjNotation,
+ _fnGetObjectDataFn: _fnGetObjectDataFn,
+ _fnSetObjectDataFn: _fnSetObjectDataFn,
+ _fnGetDataMaster: _fnGetDataMaster,
+ _fnClearTable: _fnClearTable,
+ _fnDeleteIndex: _fnDeleteIndex,
+ _fnInvalidate: _fnInvalidate,
+ _fnGetRowElements: _fnGetRowElements,
+ _fnCreateTr: _fnCreateTr,
+ _fnBuildHead: _fnBuildHead,
+ _fnDrawHead: _fnDrawHead,
+ _fnDraw: _fnDraw,
+ _fnReDraw: _fnReDraw,
+ _fnAddOptionsHtml: _fnAddOptionsHtml,
+ _fnDetectHeader: _fnDetectHeader,
+ _fnGetUniqueThs: _fnGetUniqueThs,
+ _fnFeatureHtmlFilter: _fnFeatureHtmlFilter,
+ _fnFilterComplete: _fnFilterComplete,
+ _fnFilterCustom: _fnFilterCustom,
+ _fnFilterColumn: _fnFilterColumn,
+ _fnFilter: _fnFilter,
+ _fnFilterCreateSearch: _fnFilterCreateSearch,
+ _fnEscapeRegex: _fnEscapeRegex,
+ _fnFilterData: _fnFilterData,
+ _fnFeatureHtmlInfo: _fnFeatureHtmlInfo,
+ _fnUpdateInfo: _fnUpdateInfo,
+ _fnInfoMacros: _fnInfoMacros,
+ _fnInitialise: _fnInitialise,
+ _fnInitComplete: _fnInitComplete,
+ _fnLengthChange: _fnLengthChange,
+ _fnFeatureHtmlLength: _fnFeatureHtmlLength,
+ _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate,
+ _fnPageChange: _fnPageChange,
+ _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing,
+ _fnProcessingDisplay: _fnProcessingDisplay,
+ _fnFeatureHtmlTable: _fnFeatureHtmlTable,
+ _fnScrollDraw: _fnScrollDraw,
+ _fnApplyToChildren: _fnApplyToChildren,
+ _fnCalculateColumnWidths: _fnCalculateColumnWidths,
+ _fnThrottle: _fnThrottle,
+ _fnConvertToWidth: _fnConvertToWidth,
+ _fnGetWidestNode: _fnGetWidestNode,
+ _fnGetMaxLenString: _fnGetMaxLenString,
+ _fnStringToCss: _fnStringToCss,
+ _fnSortFlatten: _fnSortFlatten,
+ _fnSort: _fnSort,
+ _fnSortAria: _fnSortAria,
+ _fnSortListener: _fnSortListener,
+ _fnSortAttachListener: _fnSortAttachListener,
+ _fnSortingClasses: _fnSortingClasses,
+ _fnSortData: _fnSortData,
+ _fnSaveState: _fnSaveState,
+ _fnLoadState: _fnLoadState,
+ _fnSettingsFromNode: _fnSettingsFromNode,
+ _fnLog: _fnLog,
+ _fnMap: _fnMap,
+ _fnBindAction: _fnBindAction,
+ _fnCallbackReg: _fnCallbackReg,
+ _fnCallbackFire: _fnCallbackFire,
+ _fnLengthOverflow: _fnLengthOverflow,
+ _fnRenderer: _fnRenderer,
+ _fnDataSource: _fnDataSource,
+ _fnRowAttributes: _fnRowAttributes,
+ _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant
+ // in 1.10, so this dead-end function is
+ // added to prevent errors
+ } );
+
+
+ // jQuery access
+ $.fn.dataTable = DataTable;
+
+ // Legacy aliases
+ $.fn.dataTableSettings = DataTable.settings;
+ $.fn.dataTableExt = DataTable.ext;
+
+ // With a capital `D` we return a DataTables API instance rather than a
+ // jQuery object
+ $.fn.DataTable = function ( opts ) {
+ return $(this).dataTable( opts ).api();
+ };
+
+ // All properties that are available to $.fn.dataTable should also be
+ // available on $.fn.DataTable
+ $.each( DataTable, function ( prop, val ) {
+ $.fn.DataTable[ prop ] = val;
+ } );
+
+
+ // Information about events fired by DataTables - for documentation.
+ /**
+ * Draw event, fired whenever the table is redrawn on the page, at the same
+ * point as fnDrawCallback. This may be useful for binding events or
+ * performing calculations when the table is altered at all.
+ * @name DataTable#draw.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ */
+
+ /**
+ * Search event, fired when the searching applied to the table (using the
+ * built-in global search, or column filters) is altered.
+ * @name DataTable#search.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ */
+
+ /**
+ * Page change event, fired when the paging of the table is altered.
+ * @name DataTable#page.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ */
+
+ /**
+ * Order event, fired when the ordering applied to the table is altered.
+ * @name DataTable#order.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ */
+
+ /**
+ * DataTables initialisation complete event, fired when the table is fully
+ * drawn, including Ajax data loaded, if Ajax data is required.
+ * @name DataTable#init.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} oSettings DataTables settings object
+ * @param {object} json The JSON object request from the server - only
+ * present if client-side Ajax sourced data is used
+ */
+
+ /**
+ * State save event, fired when the table has changed state a new state save
+ * is required. This event allows modification of the state saving object
+ * prior to actually doing the save, including addition or other state
+ * properties (for plug-ins) or modification of a DataTables core property.
+ * @name DataTable#stateSaveParams.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} oSettings DataTables settings object
+ * @param {object} json The state information to be saved
+ */
+
+ /**
+ * State load event, fired when the table is loading state from the stored
+ * data, but prior to the settings object being modified by the saved state
+ * - allowing modification of the saved state is required or loading of
+ * state for a plug-in.
+ * @name DataTable#stateLoadParams.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} oSettings DataTables settings object
+ * @param {object} json The saved state information
+ */
+
+ /**
+ * State loaded event, fired when state has been loaded from stored data and
+ * the settings object has been modified by the loaded data.
+ * @name DataTable#stateLoaded.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} oSettings DataTables settings object
+ * @param {object} json The saved state information
+ */
+
+ /**
+ * Processing event, fired when DataTables is doing some kind of processing
+ * (be it, order, searcg or anything else). It can be used to indicate to
+ * the end user that there is something happening, or that something has
+ * finished.
+ * @name DataTable#processing.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} oSettings DataTables settings object
+ * @param {boolean} bShow Flag for if DataTables is doing processing or not
+ */
+
+ /**
+ * Ajax (XHR) event, fired whenever an Ajax request is completed from a
+ * request to made to the server for new data. This event is called before
+ * DataTables processed the returned data, so it can also be used to pre-
+ * process the data returned from the server, if needed.
+ *
+ * Note that this trigger is called in `fnServerData`, if you override
+ * `fnServerData` and which to use this event, you need to trigger it in you
+ * success function.
+ * @name DataTable#xhr.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ * @param {object} json JSON returned from the server
+ *
+ * @example
+ * // Use a custom property returned from the server in another DOM element
+ * $('#table').dataTable().on('xhr.dt', function (e, settings, json) {
+ * $('#status').html( json.status );
+ * } );
+ *
+ * @example
+ * // Pre-process the data returned from the server
+ * $('#table').dataTable().on('xhr.dt', function (e, settings, json) {
+ * for ( var i=0, ien=json.aaData.length ; i 1 && countSelectedRows > 0 && !$('#groupTeamsTableToolbarContainer').hasClass('opened')) {
+// $('#usersManagementPortletContainerSiteTeamsEditMode #groupTeamsTableToolbarContainer')
+// .removeClass('hiddenToolbar').addClass('shownToolbar');
+ $('#groupTeamsTableToolbarContainer').addClass('opened');
+ $('#groupTeamsTableToolbarContainer').animate({height:'show'});
+ } else if(countSelectedRows === 0) {
+// $('#usersManagementPortletContainerSiteTeamsEditMode #groupTeamsTableToolbarContainer').addClass('hiddenToolbar')
+// .removeClass('shownToolbar');
+ $('#groupTeamsTableToolbarContainer').animate({height:'hide'});
+ $('#groupTeamsTableToolbarContainer').removeClass('opened');
+ }
+ });
+
+ $('#usersManagementPortletContainerSiteTeamsEditMode table#GroupTeamsTable tbody')
+ .on(
+ 'click',
+ 'tr:not(tr.control) td:not(:first-of-type)', function(e){
+
+ var table = $('table#GroupTeamsTable');
+ var theTable = $('table#GroupTeamsTableUsers');
+ theTable.DataTable().clear();
+ theTable.DataTable().columns.adjust().draw();
+ theTable.DataTable().columns.adjust().responsive.recalc();
+ var selectedTr = $(e.target).closest('tr')[0];
+ var data = table.dataTable().fnGetData(selectedTr);
+ var usersHtml = data.siteTeamUsers;
+ var usersJQuery = $($.parseHTML(usersHtml));
+ var usersDetails = usersJQuery.find('p');
+ if(usersDetails.length > 0){
+ for(var i=0; i');
+ this.$input = $(' ').appendTo(this.$container);
+
+ this.$element.before(this.$container);
+
+ this.build(options);
+ }
+
+ TagsInput.prototype = {
+ constructor: TagsInput,
+
+ /**
+ * Adds the given item as a new tag. Pass true to dontPushVal to prevent
+ * updating the elements val()
+ */
+ add: function(item, dontPushVal, options) {
+ var self = this;
+
+ if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
+ return;
+
+ // Ignore falsey values, except false
+ if (item !== false && !item)
+ return;
+
+ // Trim value
+ if (typeof item === "string" && self.options.trimValue) {
+ item = $.trim(item);
+ }
+
+ // Throw an error when trying to add an object while the itemValue option was not set
+ if (typeof item === "object" && !self.objectItems)
+ throw("Can't add objects when itemValue option is not set");
+
+ // Ignore strings only containg whitespace
+ if (item.toString().match(/^\s*$/))
+ return;
+
+ // If SELECT but not multiple, remove current tag
+ if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
+ self.remove(self.itemsArray[0]);
+
+ if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
+ var items = item.split(',');
+ if (items.length > 1) {
+ for (var i = 0; i < items.length; i++) {
+ this.add(items[i], true);
+ }
+
+ if (!dontPushVal)
+ self.pushVal();
+ return;
+ }
+ }
+
+ var itemValue = self.options.itemValue(item),
+ itemText = self.options.itemText(item),
+ tagClass = self.options.tagClass(item),
+ itemTitle = self.options.itemTitle(item);
+
+ // Ignore items allready added
+ var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
+ if (existing && !self.options.allowDuplicates) {
+ // Invoke onTagExists
+ if (self.options.onTagExists) {
+ var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
+ self.options.onTagExists(item, $existingTag);
+ }
+ return;
+ }
+
+ // if length greater than limit
+ if (self.items().toString().length + item.length + 1 > self.options.maxInputLength)
+ return;
+
+ // raise beforeItemAdd arg
+ var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options});
+ self.$element.trigger(beforeItemAddEvent);
+ if (beforeItemAddEvent.cancel)
+ return;
+
+ // register item in internal array and map
+ self.itemsArray.push(item);
+
+ // add a tag element
+
+ var $tag = $('' + htmlEncode(itemText) + ' ');
+ $tag.data('item', item);
+ self.findInputWrapper().before($tag);
+ $tag.after(' ');
+
+ // add if item represents a value not present in one of the 's options
+ if (self.isSelect && !$('option[value="' + encodeURIComponent(itemValue) + '"]',self.$element)[0]) {
+ var $option = $('' + htmlEncode(itemText) + ' ');
+ $option.data('item', item);
+ $option.attr('value', itemValue);
+ self.$element.append($option);
+ }
+
+ if (!dontPushVal)
+ self.pushVal();
+
+ // Add class when reached maxTags
+ if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength)
+ self.$container.addClass('bootstrap-tagsinput-max');
+
+ self.$element.trigger($.Event('itemAdded', { item: item, options: options }));
+ },
+
+ /**
+ * Removes the given item. Pass true to dontPushVal to prevent updating the
+ * elements val()
+ */
+ remove: function(item, dontPushVal, options) {
+ var self = this;
+
+ if (self.objectItems) {
+ if (typeof item === "object")
+ item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } );
+ else
+ item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } );
+
+ item = item[item.length-1];
+ }
+
+ if (item) {
+ var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false, options: options });
+ self.$element.trigger(beforeItemRemoveEvent);
+ if (beforeItemRemoveEvent.cancel)
+ return;
+
+ $('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
+ $('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
+ if($.inArray(item, self.itemsArray) !== -1)
+ self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
+ }
+
+ if (!dontPushVal)
+ self.pushVal();
+
+ // Remove class when reached maxTags
+ if (self.options.maxTags > self.itemsArray.length)
+ self.$container.removeClass('bootstrap-tagsinput-max');
+
+ self.$element.trigger($.Event('itemRemoved', { item: item, options: options }));
+ },
+
+ /**
+ * Removes all items
+ */
+ removeAll: function() {
+ var self = this;
+
+ $('.tag', self.$container).remove();
+ $('option', self.$element).remove();
+
+ while(self.itemsArray.length > 0)
+ self.itemsArray.pop();
+
+ self.pushVal();
+ },
+
+ /**
+ * Refreshes the tags so they match the text/value of their corresponding
+ * item.
+ */
+ refresh: function() {
+ var self = this;
+ $('.tag', self.$container).each(function() {
+ var $tag = $(this),
+ item = $tag.data('item'),
+ itemValue = self.options.itemValue(item),
+ itemText = self.options.itemText(item),
+ tagClass = self.options.tagClass(item);
+
+ // Update tag's class and inner text
+ $tag.attr('class', null);
+ $tag.addClass('tag ' + htmlEncode(tagClass));
+ $tag.contents().filter(function() {
+ return this.nodeType == 3;
+ })[0].nodeValue = htmlEncode(itemText);
+
+ if (self.isSelect) {
+ var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
+ option.attr('value', itemValue);
+ }
+ });
+ },
+
+ /**
+ * Returns the items added as tags
+ */
+ items: function() {
+ return this.itemsArray;
+ },
+
+ /**
+ * Assembly value by retrieving the value of each item, and set it on the
+ * element.
+ */
+ pushVal: function() {
+ var self = this,
+ val = $.map(self.items(), function(item) {
+ return self.options.itemValue(item).toString();
+ });
+
+ self.$element.val(val, true).trigger('change');
+ },
+
+ /**
+ * Initializes the tags input behaviour on the element
+ */
+ build: function(options) {
+ var self = this;
+
+ self.options = $.extend({}, defaultOptions, options);
+ // When itemValue is set, freeInput should always be false
+ if (self.objectItems)
+ self.options.freeInput = false;
+
+ makeOptionItemFunction(self.options, 'itemValue');
+ makeOptionItemFunction(self.options, 'itemText');
+ makeOptionFunction(self.options, 'tagClass');
+
+ // Typeahead Bootstrap version 2.3.2
+ if (self.options.typeahead) {
+ var typeahead = self.options.typeahead || {};
+
+ makeOptionFunction(typeahead, 'source');
+
+ self.$input.typeahead($.extend({}, typeahead, {
+ source: function (query, process) {
+ function processItems(items) {
+ var texts = [];
+
+ for (var i = 0; i < items.length; i++) {
+ var text = self.options.itemText(items[i]);
+ map[text] = items[i];
+ texts.push(text);
+ }
+ process(texts);
+ }
+
+ this.map = {};
+ var map = this.map,
+ data = typeahead.source(query);
+
+ if ($.isFunction(data.success)) {
+ // support for Angular callbacks
+ data.success(processItems);
+ } else if ($.isFunction(data.then)) {
+ // support for Angular promises
+ data.then(processItems);
+ } else {
+ // support for functions and jquery promises
+ $.when(data)
+ .then(processItems);
+ }
+ },
+ updater: function (text) {
+ self.add(this.map[text]);
+ return this.map[text];
+ },
+ matcher: function (text) {
+ return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
+ },
+ sorter: function (texts) {
+ return texts.sort();
+ },
+ highlighter: function (text) {
+ var regex = new RegExp( '(' + this.query + ')', 'gi' );
+ return text.replace( regex, "$1 " );
+ }
+ }));
+ }
+
+ // typeahead.js
+ if (self.options.typeaheadjs) {
+ var typeaheadConfig = null;
+ var typeaheadDatasets = {};
+
+ // Determine if main configurations were passed or simply a dataset
+ var typeaheadjs = self.options.typeaheadjs;
+ if ($.isArray(typeaheadjs)) {
+ typeaheadConfig = typeaheadjs[0];
+ typeaheadDatasets = typeaheadjs[1];
+ } else {
+ typeaheadDatasets = typeaheadjs;
+ }
+
+ self.$input.typeahead(typeaheadConfig, typeaheadDatasets).on('typeahead:selected', $.proxy(function (obj, datum) {
+ if (typeaheadDatasets.valueKey)
+ self.add(datum[typeaheadDatasets.valueKey]);
+ else
+ self.add(datum);
+ self.$input.typeahead('val', '');
+ }, self));
+ }
+
+ self.$container.on('click', $.proxy(function(event) {
+ if (! self.$element.attr('disabled')) {
+ self.$input.removeAttr('disabled');
+ }
+ self.$input.focus();
+ }, self));
+
+ if (self.options.addOnBlur && self.options.freeInput) {
+ self.$input.on('focusout', $.proxy(function(event) {
+ // HACK: only process on focusout when no typeahead opened, to
+ // avoid adding the typeahead text as tag
+ if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) {
+ self.add(self.$input.val());
+ self.$input.val('');
+ }
+ }, self));
+ }
+
+
+ self.$container.on('keydown', 'input', $.proxy(function(event) {
+ var $input = $(event.target),
+ $inputWrapper = self.findInputWrapper();
+
+ if (self.$element.attr('disabled')) {
+ self.$input.attr('disabled', 'disabled');
+ return;
+ }
+
+ switch (event.which) {
+ // BACKSPACE
+ case 8:
+ if (doGetCaretPosition($input[0]) === 0) {
+ var prev = $inputWrapper.prev();
+ if (prev) {
+ self.remove(prev.data('item'));
+ }
+ }
+ break;
+
+ // DELETE
+ case 46:
+ if (doGetCaretPosition($input[0]) === 0) {
+ var next = $inputWrapper.next();
+ if (next) {
+ self.remove(next.data('item'));
+ }
+ }
+ break;
+
+ // LEFT ARROW
+ case 37:
+ // Try to move the input before the previous tag
+ var $prevTag = $inputWrapper.prev();
+ if ($input.val().length === 0 && $prevTag[0]) {
+ $prevTag.before($inputWrapper);
+ $input.focus();
+ }
+ break;
+ // RIGHT ARROW
+ case 39:
+ // Try to move the input after the next tag
+ var $nextTag = $inputWrapper.next();
+ if ($input.val().length === 0 && $nextTag[0]) {
+ $nextTag.after($inputWrapper);
+ $input.focus();
+ }
+ break;
+ default:
+ // ignore
+ }
+
+ // Reset internal input's size
+ var textLength = $input.val().length,
+ wordSpace = Math.ceil(textLength / 5),
+ size = textLength + wordSpace + 1;
+ $input.attr('size', Math.max(this.inputSize, $input.val().length));
+ }, self));
+
+ self.$container.on('keypress', 'input', $.proxy(function(event) {
+ var $input = $(event.target);
+
+ if (self.$element.attr('disabled')) {
+ self.$input.attr('disabled', 'disabled');
+ return;
+ }
+
+ var text = $input.val(),
+ maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
+ if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
+ self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
+ $input.val('');
+ event.preventDefault();
+ }
+
+ // Reset internal input's size
+ var textLength = $input.val().length,
+ wordSpace = Math.ceil(textLength / 5),
+ size = textLength + wordSpace + 1;
+ $input.attr('size', Math.max(this.inputSize, $input.val().length));
+ }, self));
+
+ // Remove icon clicked
+ self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
+ if (self.$element.attr('disabled')) {
+ return;
+ }
+ self.remove($(event.target).closest('.tag').data('item'));
+ }, self));
+
+ // Only add existing value as tags when using strings as tags
+ if (self.options.itemValue === defaultOptions.itemValue) {
+ if (self.$element[0].tagName === 'INPUT') {
+ self.add(self.$element.val());
+ } else {
+ $('option', self.$element).each(function() {
+ self.add($(this).attr('value'), true);
+ });
+ }
+ }
+ },
+
+ /**
+ * Removes all tagsinput behaviour and unregsiter all event handlers
+ */
+ destroy: function() {
+ var self = this;
+
+ // Unbind events
+ self.$container.off('keypress', 'input');
+ self.$container.off('click', '[role=remove]');
+
+ self.$container.remove();
+ self.$element.removeData('tagsinput');
+ self.$element.show();
+ },
+
+ /**
+ * Sets focus on the tagsinput
+ */
+ focus: function() {
+ this.$input.focus();
+ },
+
+ /**
+ * Returns the internal input element
+ */
+ input: function() {
+ return this.$input;
+ },
+
+ /**
+ * Returns the element which is wrapped around the internal input. This
+ * is normally the $container, but typeahead.js moves the $input element.
+ */
+ findInputWrapper: function() {
+ var elt = this.$input[0],
+ container = this.$container[0];
+ while(elt && elt.parentNode !== container)
+ elt = elt.parentNode;
+
+ return $(elt);
+ }
+ };
+
+ /**
+ * Register JQuery plugin
+ */
+ $.fn.tagsinput = function(arg1, arg2, arg3) {
+ var results = [];
+
+ this.each(function() {
+ var tagsinput = $(this).data('tagsinput');
+ // Initialize a new tags input
+ if (!tagsinput) {
+ tagsinput = new TagsInput(this, arg1);
+ $(this).data('tagsinput', tagsinput);
+ results.push(tagsinput);
+
+ if (this.tagName === 'SELECT') {
+ $('option', $(this)).attr('selected', 'selected');
+ }
+
+ // Init tags from $(this).val()
+ $(this).val($(this).val());
+ } else if (!arg1 && !arg2) {
+ // tagsinput already exists
+ // no function, trying to init
+ results.push(tagsinput);
+ } else if(tagsinput[arg1] !== undefined) {
+ // Invoke function on existing tags input
+ if(tagsinput[arg1].length === 3 && arg3 !== undefined){
+ var retVal = tagsinput[arg1](arg2, null, arg3);
+ }else{
+ var retVal = tagsinput[arg1](arg2);
+ }
+ if (retVal !== undefined)
+ results.push(retVal);
+ }
+ });
+
+ if ( typeof arg1 == 'string') {
+ // Return the results from the invoked function calls
+ return results.length > 1 ? results : results[0];
+ } else {
+ return results;
+ }
+ };
+
+ $.fn.tagsinput.Constructor = TagsInput;
+
+ /**
+ * Most options support both a string or number as well as a function as
+ * option value. This function makes sure that the option with the given
+ * key in the given options is wrapped in a function
+ */
+ function makeOptionItemFunction(options, key) {
+ if (typeof options[key] !== 'function') {
+ var propertyName = options[key];
+ options[key] = function(item) { return item[propertyName]; };
+ }
+ }
+ function makeOptionFunction(options, key) {
+ if (typeof options[key] !== 'function') {
+ var value = options[key];
+ options[key] = function() { return value; };
+ }
+ }
+ /**
+ * HtmlEncodes the given value
+ */
+ var htmlEncodeContainer = $('
');
+ function htmlEncode(value) {
+ if (value) {
+ return htmlEncodeContainer.text(value).html();
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Returns the position of the caret in the given input field
+ * http://flightschool.acylt.com/devnotes/caret-position-woes/
+ */
+ function doGetCaretPosition(oField) {
+ var iCaretPos = 0;
+ if (document.selection) {
+ oField.focus ();
+ var oSel = document.selection.createRange();
+ oSel.moveStart ('character', -oField.value.length);
+ iCaretPos = oSel.text.length;
+ } else if (oField.selectionStart || oField.selectionStart == '0') {
+ iCaretPos = oField.selectionStart;
+ }
+ return (iCaretPos);
+ }
+
+ /**
+ * Returns boolean indicates whether user has pressed an expected key combination.
+ * @param object keyPressEvent: JavaScript event object, refer
+ * http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+ * @param object lookupList: expected key combinations, as in:
+ * [13, {which: 188, shiftKey: true}]
+ */
+ function keyCombinationInList(keyPressEvent, lookupList) {
+ var found = false;
+ $.each(lookupList, function (index, keyCombination) {
+ if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
+ found = true;
+ return false;
+ }
+
+ if (keyPressEvent.which === keyCombination.which) {
+ var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
+ shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
+ ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
+ if (alt && shift && ctrl) {
+ found = true;
+ return false;
+ }
+ }
+ });
+
+ return found;
+ }
+
+ /**
+ * Initialize tagsinput behaviour on inputs and selects which have
+ * data-role=tagsinput
+ */
+ $(function() {
+ $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
+ });
+})(window.jQuery);
diff --git a/src/main/webapp/js/src/bootstrap-wysihtml5.js b/src/main/webapp/js/src/bootstrap-wysihtml5.js
new file mode 100644
index 0000000..28bcd74
--- /dev/null
+++ b/src/main/webapp/js/src/bootstrap-wysihtml5.js
@@ -0,0 +1,511 @@
+!function($, wysi) {
+ "use strict";
+
+ var tpl = {
+ "font-styles": function(locale, options) {
+ var size = (options && options.size) ? ' btn-'+options.size : '';
+ return "" +
+ "" +
+ " " + locale.font_styles.normal + " " +
+ " " +
+ "" +
+ " ";
+ },
+
+ "emphasis": function(locale, options) {
+ var size = (options && options.size) ? ' btn-'+options.size : '';
+ return "" +
+ "" +
+ " ";
+ },
+
+ "lists": function(locale, options) {
+ var size = (options && options.size) ? ' btn-'+options.size : '';
+ return "" +
+ "" +
+ "
" +
+ "
" +
+ "
" +
+ "
" +
+ "
" +
+ " ";
+ },
+
+ "link": function(locale, options) {
+ var size = (options && options.size) ? ' btn-'+options.size : '';
+ return "" +
+ "" +
+ " " +
+ " ";
+ },
+
+ "image": function(locale, options) {
+ var size = (options && options.size) ? ' btn-'+options.size : '';
+ return "" +
+ "" +
+ "" +
+ "
" +
+ " " +
+ "
" +
+ "" +
+ "
" +
+ " " +
+ " ";
+ },
+
+ "html": function(locale, options) {
+ var size = (options && options.size) ? ' btn-'+options.size : '';
+ return "" +
+ "" +
+ " ";
+ },
+
+ "color": function(locale, options) {
+ var size = (options && options.size) ? ' btn-'+options.size : '';
+ return "" +
+ "" +
+ "" + locale.colours.black + " " +
+ " " +
+ "" +
+ " ";
+ }
+ };
+
+ var templates = function(key, locale, options) {
+ return tpl[key](locale, options);
+ };
+
+
+ var Wysihtml5 = function(el, options) {
+ this.el = el;
+ var toolbarOpts = options || defaultOptions;
+ for(var t in toolbarOpts.customTemplates) {
+ tpl[t] = toolbarOpts.customTemplates[t];
+ }
+ this.toolbar = this.createToolbar(el, toolbarOpts);
+ this.editor = this.createEditor(options);
+
+ window.editor = this.editor;
+
+ $('iframe.wysihtml5-sandbox').each(function(i, el){
+ $(el.contentWindow).off('focus.wysihtml5').on({
+ 'focus.wysihtml5' : function(){
+ $('li.dropdown').removeClass('open');
+ }
+ });
+ });
+ };
+
+ Wysihtml5.prototype = {
+
+ constructor: Wysihtml5,
+
+ createEditor: function(options) {
+ options = options || {};
+
+ // Add the toolbar to a clone of the options object so multiple instances
+ // of the WYISYWG don't break because "toolbar" is already defined
+ options = $.extend(true, {}, options);
+ options.toolbar = this.toolbar[0];
+
+ var editor = new wysi.Editor(this.el[0], options);
+
+ if(options && options.events) {
+ for(var eventName in options.events) {
+ editor.on(eventName, options.events[eventName]);
+ }
+ }
+ return editor;
+ },
+
+ createToolbar: function(el, options) {
+ var self = this;
+ var toolbar = $("", {
+ 'class' : "wysihtml5-toolbar",
+ 'style': "display:none"
+ });
+ var culture = options.locale || defaultOptions.locale || "en";
+ for(var key in defaultOptions) {
+ var value = false;
+
+ if(options[key] !== undefined) {
+ if(options[key] === true) {
+ value = true;
+ }
+ } else {
+ value = defaultOptions[key];
+ }
+
+ if(value === true) {
+ toolbar.append(templates(key, locale[culture], options));
+
+ if(key === "html") {
+ this.initHtml(toolbar);
+ }
+
+ if(key === "link") {
+ this.initInsertLink(toolbar);
+ }
+
+ if(key === "image") {
+ this.initInsertImage(toolbar);
+ }
+ }
+ }
+
+ if(options.toolbar) {
+ for(key in options.toolbar) {
+ toolbar.append(options.toolbar[key]);
+ }
+ }
+
+ toolbar.find("a[data-wysihtml5-command='formatBlock']").click(function(e) {
+ var target = e.target || e.srcElement;
+ var el = $(target);
+ self.toolbar.find('.current-font').text(el.html());
+ });
+
+ toolbar.find("a[data-wysihtml5-command='foreColor']").click(function(e) {
+ var target = e.target || e.srcElement;
+ var el = $(target);
+ self.toolbar.find('.current-color').text(el.html());
+ });
+
+ this.el.before(toolbar);
+
+ return toolbar;
+ },
+
+ initHtml: function(toolbar) {
+ var changeViewSelector = "a[data-wysihtml5-action='change_view']";
+ toolbar.find(changeViewSelector).click(function(e) {
+ toolbar.find('a.btn').not(changeViewSelector).toggleClass('disabled');
+ });
+ },
+
+ initInsertImage: function(toolbar) {
+ var self = this;
+ var insertImageModal = toolbar.find('.bootstrap-wysihtml5-insert-image-modal');
+ var urlInput = insertImageModal.find('.bootstrap-wysihtml5-insert-image-url');
+ var insertButton = insertImageModal.find('a.btn-primary');
+ var initialValue = urlInput.val();
+ var caretBookmark;
+
+ var insertImage = function() {
+ var url = urlInput.val();
+ urlInput.val(initialValue);
+ self.editor.currentView.element.focus();
+ if (caretBookmark) {
+ self.editor.composer.selection.setBookmark(caretBookmark);
+ caretBookmark = null;
+ }
+ self.editor.composer.commands.exec("insertImage", url);
+ };
+
+ urlInput.keypress(function(e) {
+ if(e.which == 13) {
+ insertImage();
+ insertImageModal.modal('hide');
+ }
+ });
+
+ insertButton.click(insertImage);
+
+ insertImageModal.on('shown', function() {
+ urlInput.focus();
+ });
+
+ insertImageModal.on('hide', function() {
+ self.editor.currentView.element.focus();
+ });
+
+ toolbar.find('a[data-wysihtml5-command=insertImage]').click(function() {
+ var activeButton = $(this).hasClass("wysihtml5-command-active");
+
+ if (!activeButton) {
+ self.editor.currentView.element.focus(false);
+ caretBookmark = self.editor.composer.selection.getBookmark();
+ insertImageModal.appendTo('body').modal('show');
+ insertImageModal.on('click.dismiss.modal', '[data-dismiss="modal"]', function(e) {
+ e.stopPropagation();
+ });
+ return false;
+ }
+ else {
+ return true;
+ }
+ });
+ },
+
+ initInsertLink: function(toolbar) {
+ var self = this;
+ var insertLinkModal = toolbar.find('.bootstrap-wysihtml5-insert-link-modal');
+ var urlInput = insertLinkModal.find('.bootstrap-wysihtml5-insert-link-url');
+ var targetInput = insertLinkModal.find('.bootstrap-wysihtml5-insert-link-target');
+ var insertButton = insertLinkModal.find('a.btn-primary');
+ var initialValue = urlInput.val();
+ var caretBookmark;
+
+ var insertLink = function() {
+ var url = urlInput.val();
+ urlInput.val(initialValue);
+ self.editor.currentView.element.focus();
+ if (caretBookmark) {
+ self.editor.composer.selection.setBookmark(caretBookmark);
+ caretBookmark = null;
+ }
+
+ var newWindow = targetInput.prop("checked");
+ self.editor.composer.commands.exec("createLink", {
+ 'href' : url,
+ 'target' : (newWindow ? '_blank' : '_self'),
+ 'rel' : (newWindow ? 'nofollow' : '')
+ });
+ };
+ var pressedEnter = false;
+
+ urlInput.keypress(function(e) {
+ if(e.which == 13) {
+ insertLink();
+ insertLinkModal.modal('hide');
+ }
+ });
+
+ insertButton.click(insertLink);
+
+ insertLinkModal.on('shown', function() {
+ urlInput.focus();
+ });
+
+ insertLinkModal.on('hide', function() {
+ self.editor.currentView.element.focus();
+ });
+
+ toolbar.find('a[data-wysihtml5-command=createLink]').click(function() {
+ var activeButton = $(this).hasClass("wysihtml5-command-active");
+
+ if (!activeButton) {
+ self.editor.currentView.element.focus(false);
+ caretBookmark = self.editor.composer.selection.getBookmark();
+ insertLinkModal.appendTo('body').modal('show');
+ insertLinkModal.on('click.dismiss.modal', '[data-dismiss="modal"]', function(e) {
+ e.stopPropagation();
+ });
+ return false;
+ }
+ else {
+ return true;
+ }
+ });
+ }
+ };
+
+ // these define our public api
+ var methods = {
+ resetDefaults: function() {
+ $.fn.wysihtml5.defaultOptions = $.extend(true, {}, $.fn.wysihtml5.defaultOptionsCache);
+ },
+ bypassDefaults: function(options) {
+ return this.each(function () {
+ var $this = $(this);
+ $this.data('wysihtml5', new Wysihtml5($this, options));
+ });
+ },
+ shallowExtend: function (options) {
+ var settings = $.extend({}, $.fn.wysihtml5.defaultOptions, options || {}, $(this).data());
+ var that = this;
+ return methods.bypassDefaults.apply(that, [settings]);
+ },
+ deepExtend: function(options) {
+ var settings = $.extend(true, {}, $.fn.wysihtml5.defaultOptions, options || {});
+ var that = this;
+ return methods.bypassDefaults.apply(that, [settings]);
+ },
+ init: function(options) {
+ var that = this;
+ return methods.shallowExtend.apply(that, [options]);
+ }
+ };
+
+ $.fn.wysihtml5 = function ( method ) {
+ if ( methods[method] ) {
+ return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
+ } else if ( typeof method === 'object' || ! method ) {
+ return methods.init.apply( this, arguments );
+ } else {
+ $.error( 'Method ' + method + ' does not exist on jQuery.wysihtml5' );
+ }
+ };
+
+ $.fn.wysihtml5.Constructor = Wysihtml5;
+
+ var defaultOptions = $.fn.wysihtml5.defaultOptions = {
+ "font-styles": true,
+ "color": false,
+ "emphasis": true,
+ "lists": true,
+ "html": false,
+ "link": true,
+ "image": true,
+ events: {},
+ parserRules: {
+ classes: {
+ // (path_to_project/lib/css/wysiwyg-color.css)
+ "wysiwyg-color-silver" : 1,
+ "wysiwyg-color-gray" : 1,
+ "wysiwyg-color-white" : 1,
+ "wysiwyg-color-maroon" : 1,
+ "wysiwyg-color-red" : 1,
+ "wysiwyg-color-purple" : 1,
+ "wysiwyg-color-fuchsia" : 1,
+ "wysiwyg-color-green" : 1,
+ "wysiwyg-color-lime" : 1,
+ "wysiwyg-color-olive" : 1,
+ "wysiwyg-color-yellow" : 1,
+ "wysiwyg-color-navy" : 1,
+ "wysiwyg-color-blue" : 1,
+ "wysiwyg-color-teal" : 1,
+ "wysiwyg-color-aqua" : 1,
+ "wysiwyg-color-orange" : 1
+ },
+ tags: {
+ "b": {},
+ "i": {},
+ "br": {},
+ "ol": {},
+ "ul": {},
+ "li": {},
+ "h1": {},
+ "h2": {},
+ "h3": {},
+ "h4": {},
+ "h5": {},
+ "h6": {},
+ "blockquote": {},
+ "u": 1,
+ "img": {
+ "check_attributes": {
+ "width": "numbers",
+ "alt": "alt",
+ "src": "url",
+ "height": "numbers"
+ }
+ },
+ "a": {
+ check_attributes: {
+ 'href': "url", // important to avoid XSS
+ 'target': 'alt',
+ 'rel': 'alt'
+ }
+ },
+ "span": 1,
+ "div": 1,
+ // to allow save and edit files with code tag hacks
+ "code": 1,
+ "pre": 1
+ }
+ },
+ stylesheets: ["./lib/css/wysiwyg-color.css"], // (path_to_project/lib/css/wysiwyg-color.css)
+ locale: "en"
+ };
+
+ if (typeof $.fn.wysihtml5.defaultOptionsCache === 'undefined') {
+ $.fn.wysihtml5.defaultOptionsCache = $.extend(true, {}, $.fn.wysihtml5.defaultOptions);
+ }
+
+ var locale = $.fn.wysihtml5.locale = {
+ en: {
+ font_styles: {
+ normal: "Normal text",
+ h1: "Heading 1",
+ h2: "Heading 2",
+ h3: "Heading 3",
+ h4: "Heading 4",
+ h5: "Heading 5",
+ h6: "Heading 6"
+ },
+ emphasis: {
+ bold: "Bold",
+ italic: "Italic",
+ underline: "Underline"
+ },
+ lists: {
+ unordered: "Unordered list",
+ ordered: "Ordered list",
+ outdent: "Outdent",
+ indent: "Indent"
+ },
+ link: {
+ insert: "Insert link",
+ cancel: "Cancel",
+ target: "Open link in new window"
+ },
+ image: {
+ insert: "Insert image",
+ cancel: "Cancel"
+ },
+ html: {
+ edit: "Edit HTML"
+ },
+ colours: {
+ black: "Black",
+ silver: "Silver",
+ gray: "Grey",
+ maroon: "Maroon",
+ red: "Red",
+ purple: "Purple",
+ green: "Green",
+ olive: "Olive",
+ navy: "Navy",
+ blue: "Blue",
+ orange: "Orange"
+ }
+ }
+ };
+
+}(window.jQuery, window.wysihtml5);
diff --git a/src/main/webapp/js/src/typeahead.bundle.js b/src/main/webapp/js/src/typeahead.bundle.js
new file mode 100644
index 0000000..bb0c8ae
--- /dev/null
+++ b/src/main/webapp/js/src/typeahead.bundle.js
@@ -0,0 +1,2451 @@
+/*!
+ * typeahead.js 0.11.1
+ * https://github.com/twitter/typeahead.js
+ * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT
+ */
+
+(function(root, factory) {
+ if (typeof define === "function" && define.amd) {
+ define("bloodhound", [ "jquery" ], function(a0) {
+ return root["Bloodhound"] = factory(a0);
+ });
+ } else if (typeof exports === "object") {
+ module.exports = factory(require("jquery"));
+ } else {
+ root["Bloodhound"] = factory(jQuery);
+ }
+})(this, function($) {
+ var _ = function() {
+ "use strict";
+ return {
+ isMsie: function() {
+ return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
+ },
+ isBlankString: function(str) {
+ return !str || /^\s*$/.test(str);
+ },
+ escapeRegExChars: function(str) {
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+ },
+ isString: function(obj) {
+ return typeof obj === "string";
+ },
+ isNumber: function(obj) {
+ return typeof obj === "number";
+ },
+ isArray: $.isArray,
+ isFunction: $.isFunction,
+ isObject: $.isPlainObject,
+ isUndefined: function(obj) {
+ return typeof obj === "undefined";
+ },
+ isElement: function(obj) {
+ return !!(obj && obj.nodeType === 1);
+ },
+ isJQuery: function(obj) {
+ return obj instanceof $;
+ },
+ toStr: function toStr(s) {
+ return _.isUndefined(s) || s === null ? "" : s + "";
+ },
+ bind: $.proxy,
+ each: function(collection, cb) {
+ $.each(collection, reverseArgs);
+ function reverseArgs(index, value) {
+ return cb(value, index);
+ }
+ },
+ map: $.map,
+ filter: $.grep,
+ every: function(obj, test) {
+ var result = true;
+ if (!obj) {
+ return result;
+ }
+ $.each(obj, function(key, val) {
+ if (!(result = test.call(null, val, key, obj))) {
+ return false;
+ }
+ });
+ return !!result;
+ },
+ some: function(obj, test) {
+ var result = false;
+ if (!obj) {
+ return result;
+ }
+ $.each(obj, function(key, val) {
+ if (result = test.call(null, val, key, obj)) {
+ return false;
+ }
+ });
+ return !!result;
+ },
+ mixin: $.extend,
+ identity: function(x) {
+ return x;
+ },
+ clone: function(obj) {
+ return $.extend(true, {}, obj);
+ },
+ getIdGenerator: function() {
+ var counter = 0;
+ return function() {
+ return counter++;
+ };
+ },
+ templatify: function templatify(obj) {
+ return $.isFunction(obj) ? obj : template;
+ function template() {
+ return String(obj);
+ }
+ },
+ defer: function(fn) {
+ setTimeout(fn, 0);
+ },
+ debounce: function(func, wait, immediate) {
+ var timeout, result;
+ return function() {
+ var context = this, args = arguments, later, callNow;
+ later = function() {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ }
+ };
+ callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if (callNow) {
+ result = func.apply(context, args);
+ }
+ return result;
+ };
+ },
+ throttle: function(func, wait) {
+ var context, args, timeout, result, previous, later;
+ previous = 0;
+ later = function() {
+ previous = new Date();
+ timeout = null;
+ result = func.apply(context, args);
+ };
+ return function() {
+ var now = new Date(), remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(context, args);
+ } else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+ },
+ stringify: function(val) {
+ return _.isString(val) ? val : JSON.stringify(val);
+ },
+ noop: function() {}
+ };
+ }();
+ var VERSION = "0.11.1";
+ var tokenizers = function() {
+ "use strict";
+ return {
+ nonword: nonword,
+ whitespace: whitespace,
+ obj: {
+ nonword: getObjTokenizer(nonword),
+ whitespace: getObjTokenizer(whitespace)
+ }
+ };
+ function whitespace(str) {
+ str = _.toStr(str);
+ return str ? str.split(/\s+/) : [];
+ }
+ function nonword(str) {
+ str = _.toStr(str);
+ return str ? str.split(/\W+/) : [];
+ }
+ function getObjTokenizer(tokenizer) {
+ return function setKey(keys) {
+ keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0);
+ return function tokenize(o) {
+ var tokens = [];
+ _.each(keys, function(k) {
+ tokens = tokens.concat(tokenizer(_.toStr(o[k])));
+ });
+ return tokens;
+ };
+ };
+ }
+ }();
+ var LruCache = function() {
+ "use strict";
+ function LruCache(maxSize) {
+ this.maxSize = _.isNumber(maxSize) ? maxSize : 100;
+ this.reset();
+ if (this.maxSize <= 0) {
+ this.set = this.get = $.noop;
+ }
+ }
+ _.mixin(LruCache.prototype, {
+ set: function set(key, val) {
+ var tailItem = this.list.tail, node;
+ if (this.size >= this.maxSize) {
+ this.list.remove(tailItem);
+ delete this.hash[tailItem.key];
+ this.size--;
+ }
+ if (node = this.hash[key]) {
+ node.val = val;
+ this.list.moveToFront(node);
+ } else {
+ node = new Node(key, val);
+ this.list.add(node);
+ this.hash[key] = node;
+ this.size++;
+ }
+ },
+ get: function get(key) {
+ var node = this.hash[key];
+ if (node) {
+ this.list.moveToFront(node);
+ return node.val;
+ }
+ },
+ reset: function reset() {
+ this.size = 0;
+ this.hash = {};
+ this.list = new List();
+ }
+ });
+ function List() {
+ this.head = this.tail = null;
+ }
+ _.mixin(List.prototype, {
+ add: function add(node) {
+ if (this.head) {
+ node.next = this.head;
+ this.head.prev = node;
+ }
+ this.head = node;
+ this.tail = this.tail || node;
+ },
+ remove: function remove(node) {
+ node.prev ? node.prev.next = node.next : this.head = node.next;
+ node.next ? node.next.prev = node.prev : this.tail = node.prev;
+ },
+ moveToFront: function(node) {
+ this.remove(node);
+ this.add(node);
+ }
+ });
+ function Node(key, val) {
+ this.key = key;
+ this.val = val;
+ this.prev = this.next = null;
+ }
+ return LruCache;
+ }();
+ var PersistentStorage = function() {
+ "use strict";
+ var LOCAL_STORAGE;
+ try {
+ LOCAL_STORAGE = window.localStorage;
+ LOCAL_STORAGE.setItem("~~~", "!");
+ LOCAL_STORAGE.removeItem("~~~");
+ } catch (err) {
+ LOCAL_STORAGE = null;
+ }
+ function PersistentStorage(namespace, override) {
+ this.prefix = [ "__", namespace, "__" ].join("");
+ this.ttlKey = "__ttl__";
+ this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix));
+ this.ls = override || LOCAL_STORAGE;
+ !this.ls && this._noop();
+ }
+ _.mixin(PersistentStorage.prototype, {
+ _prefix: function(key) {
+ return this.prefix + key;
+ },
+ _ttlKey: function(key) {
+ return this._prefix(key) + this.ttlKey;
+ },
+ _noop: function() {
+ this.get = this.set = this.remove = this.clear = this.isExpired = _.noop;
+ },
+ _safeSet: function(key, val) {
+ try {
+ this.ls.setItem(key, val);
+ } catch (err) {
+ if (err.name === "QuotaExceededError") {
+ this.clear();
+ this._noop();
+ }
+ }
+ },
+ get: function(key) {
+ if (this.isExpired(key)) {
+ this.remove(key);
+ }
+ return decode(this.ls.getItem(this._prefix(key)));
+ },
+ set: function(key, val, ttl) {
+ if (_.isNumber(ttl)) {
+ this._safeSet(this._ttlKey(key), encode(now() + ttl));
+ } else {
+ this.ls.removeItem(this._ttlKey(key));
+ }
+ return this._safeSet(this._prefix(key), encode(val));
+ },
+ remove: function(key) {
+ this.ls.removeItem(this._ttlKey(key));
+ this.ls.removeItem(this._prefix(key));
+ return this;
+ },
+ clear: function() {
+ var i, keys = gatherMatchingKeys(this.keyMatcher);
+ for (i = keys.length; i--; ) {
+ this.remove(keys[i]);
+ }
+ return this;
+ },
+ isExpired: function(key) {
+ var ttl = decode(this.ls.getItem(this._ttlKey(key)));
+ return _.isNumber(ttl) && now() > ttl ? true : false;
+ }
+ });
+ return PersistentStorage;
+ function now() {
+ return new Date().getTime();
+ }
+ function encode(val) {
+ return JSON.stringify(_.isUndefined(val) ? null : val);
+ }
+ function decode(val) {
+ return $.parseJSON(val);
+ }
+ function gatherMatchingKeys(keyMatcher) {
+ var i, key, keys = [], len = LOCAL_STORAGE.length;
+ for (i = 0; i < len; i++) {
+ if ((key = LOCAL_STORAGE.key(i)).match(keyMatcher)) {
+ keys.push(key.replace(keyMatcher, ""));
+ }
+ }
+ return keys;
+ }
+ }();
+ var Transport = function() {
+ "use strict";
+ var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10);
+ function Transport(o) {
+ o = o || {};
+ this.cancelled = false;
+ this.lastReq = null;
+ this._send = o.transport;
+ this._get = o.limiter ? o.limiter(this._get) : this._get;
+ this._cache = o.cache === false ? new LruCache(0) : sharedCache;
+ }
+ Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
+ maxPendingRequests = num;
+ };
+ Transport.resetCache = function resetCache() {
+ sharedCache.reset();
+ };
+ _.mixin(Transport.prototype, {
+ _fingerprint: function fingerprint(o) {
+ o = o || {};
+ return o.url + o.type + $.param(o.data || {});
+ },
+ _get: function(o, cb) {
+ var that = this, fingerprint, jqXhr;
+ fingerprint = this._fingerprint(o);
+ if (this.cancelled || fingerprint !== this.lastReq) {
+ return;
+ }
+ if (jqXhr = pendingRequests[fingerprint]) {
+ jqXhr.done(done).fail(fail);
+ } else if (pendingRequestsCount < maxPendingRequests) {
+ pendingRequestsCount++;
+ pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always);
+ } else {
+ this.onDeckRequestArgs = [].slice.call(arguments, 0);
+ }
+ function done(resp) {
+ cb(null, resp);
+ that._cache.set(fingerprint, resp);
+ }
+ function fail() {
+ cb(true);
+ }
+ function always() {
+ pendingRequestsCount--;
+ delete pendingRequests[fingerprint];
+ if (that.onDeckRequestArgs) {
+ that._get.apply(that, that.onDeckRequestArgs);
+ that.onDeckRequestArgs = null;
+ }
+ }
+ },
+ get: function(o, cb) {
+ var resp, fingerprint;
+ cb = cb || $.noop;
+ o = _.isString(o) ? {
+ url: o
+ } : o || {};
+ fingerprint = this._fingerprint(o);
+ this.cancelled = false;
+ this.lastReq = fingerprint;
+ if (resp = this._cache.get(fingerprint)) {
+ cb(null, resp);
+ } else {
+ this._get(o, cb);
+ }
+ },
+ cancel: function() {
+ this.cancelled = true;
+ }
+ });
+ return Transport;
+ }();
+ var SearchIndex = window.SearchIndex = function() {
+ "use strict";
+ var CHILDREN = "c", IDS = "i";
+ function SearchIndex(o) {
+ o = o || {};
+ if (!o.datumTokenizer || !o.queryTokenizer) {
+ $.error("datumTokenizer and queryTokenizer are both required");
+ }
+ this.identify = o.identify || _.stringify;
+ this.datumTokenizer = o.datumTokenizer;
+ this.queryTokenizer = o.queryTokenizer;
+ this.reset();
+ }
+ _.mixin(SearchIndex.prototype, {
+ bootstrap: function bootstrap(o) {
+ this.datums = o.datums;
+ this.trie = o.trie;
+ },
+ add: function(data) {
+ var that = this;
+ data = _.isArray(data) ? data : [ data ];
+ _.each(data, function(datum) {
+ var id, tokens;
+ that.datums[id = that.identify(datum)] = datum;
+ tokens = normalizeTokens(that.datumTokenizer(datum));
+ _.each(tokens, function(token) {
+ var node, chars, ch;
+ node = that.trie;
+ chars = token.split("");
+ while (ch = chars.shift()) {
+ node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode());
+ node[IDS].push(id);
+ }
+ });
+ });
+ },
+ get: function get(ids) {
+ var that = this;
+ return _.map(ids, function(id) {
+ return that.datums[id];
+ });
+ },
+ search: function search(query) {
+ var that = this, tokens, matches;
+ tokens = normalizeTokens(this.queryTokenizer(query));
+ _.each(tokens, function(token) {
+ var node, chars, ch, ids;
+ if (matches && matches.length === 0) {
+ return false;
+ }
+ node = that.trie;
+ chars = token.split("");
+ while (node && (ch = chars.shift())) {
+ node = node[CHILDREN][ch];
+ }
+ if (node && chars.length === 0) {
+ ids = node[IDS].slice(0);
+ matches = matches ? getIntersection(matches, ids) : ids;
+ } else {
+ matches = [];
+ return false;
+ }
+ });
+ return matches ? _.map(unique(matches), function(id) {
+ return that.datums[id];
+ }) : [];
+ },
+ all: function all() {
+ var values = [];
+ for (var key in this.datums) {
+ values.push(this.datums[key]);
+ }
+ return values;
+ },
+ reset: function reset() {
+ this.datums = {};
+ this.trie = newNode();
+ },
+ serialize: function serialize() {
+ return {
+ datums: this.datums,
+ trie: this.trie
+ };
+ }
+ });
+ return SearchIndex;
+ function normalizeTokens(tokens) {
+ tokens = _.filter(tokens, function(token) {
+ return !!token;
+ });
+ tokens = _.map(tokens, function(token) {
+ return token.toLowerCase();
+ });
+ return tokens;
+ }
+ function newNode() {
+ var node = {};
+ node[IDS] = [];
+ node[CHILDREN] = {};
+ return node;
+ }
+ function unique(array) {
+ var seen = {}, uniques = [];
+ for (var i = 0, len = array.length; i < len; i++) {
+ if (!seen[array[i]]) {
+ seen[array[i]] = true;
+ uniques.push(array[i]);
+ }
+ }
+ return uniques;
+ }
+ function getIntersection(arrayA, arrayB) {
+ var ai = 0, bi = 0, intersection = [];
+ arrayA = arrayA.sort();
+ arrayB = arrayB.sort();
+ var lenArrayA = arrayA.length, lenArrayB = arrayB.length;
+ while (ai < lenArrayA && bi < lenArrayB) {
+ if (arrayA[ai] < arrayB[bi]) {
+ ai++;
+ } else if (arrayA[ai] > arrayB[bi]) {
+ bi++;
+ } else {
+ intersection.push(arrayA[ai]);
+ ai++;
+ bi++;
+ }
+ }
+ return intersection;
+ }
+ }();
+ var Prefetch = function() {
+ "use strict";
+ var keys;
+ keys = {
+ data: "data",
+ protocol: "protocol",
+ thumbprint: "thumbprint"
+ };
+ function Prefetch(o) {
+ this.url = o.url;
+ this.ttl = o.ttl;
+ this.cache = o.cache;
+ this.prepare = o.prepare;
+ this.transform = o.transform;
+ this.transport = o.transport;
+ this.thumbprint = o.thumbprint;
+ this.storage = new PersistentStorage(o.cacheKey);
+ }
+ _.mixin(Prefetch.prototype, {
+ _settings: function settings() {
+ return {
+ url: this.url,
+ type: "GET",
+ dataType: "json"
+ };
+ },
+ store: function store(data) {
+ if (!this.cache) {
+ return;
+ }
+ this.storage.set(keys.data, data, this.ttl);
+ this.storage.set(keys.protocol, location.protocol, this.ttl);
+ this.storage.set(keys.thumbprint, this.thumbprint, this.ttl);
+ },
+ fromCache: function fromCache() {
+ var stored = {}, isExpired;
+ if (!this.cache) {
+ return null;
+ }
+ stored.data = this.storage.get(keys.data);
+ stored.protocol = this.storage.get(keys.protocol);
+ stored.thumbprint = this.storage.get(keys.thumbprint);
+ isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol;
+ return stored.data && !isExpired ? stored.data : null;
+ },
+ fromNetwork: function(cb) {
+ var that = this, settings;
+ if (!cb) {
+ return;
+ }
+ settings = this.prepare(this._settings());
+ this.transport(settings).fail(onError).done(onResponse);
+ function onError() {
+ cb(true);
+ }
+ function onResponse(resp) {
+ cb(null, that.transform(resp));
+ }
+ },
+ clear: function clear() {
+ this.storage.clear();
+ return this;
+ }
+ });
+ return Prefetch;
+ }();
+ var Remote = function() {
+ "use strict";
+ function Remote(o) {
+ this.url = o.url;
+ this.prepare = o.prepare;
+ this.transform = o.transform;
+ this.transport = new Transport({
+ cache: o.cache,
+ limiter: o.limiter,
+ transport: o.transport
+ });
+ }
+ _.mixin(Remote.prototype, {
+ _settings: function settings() {
+ return {
+ url: this.url,
+ type: "GET",
+ dataType: "json"
+ };
+ },
+ get: function get(query, cb) {
+ var that = this, settings;
+ if (!cb) {
+ return;
+ }
+ query = query || "";
+ settings = this.prepare(query, this._settings());
+ return this.transport.get(settings, onResponse);
+ function onResponse(err, resp) {
+ err ? cb([]) : cb(that.transform(resp));
+ }
+ },
+ cancelLastRequest: function cancelLastRequest() {
+ this.transport.cancel();
+ }
+ });
+ return Remote;
+ }();
+ var oParser = function() {
+ "use strict";
+ return function parse(o) {
+ var defaults, sorter;
+ defaults = {
+ initialize: true,
+ identify: _.stringify,
+ datumTokenizer: null,
+ queryTokenizer: null,
+ sufficient: 5,
+ sorter: null,
+ local: [],
+ prefetch: null,
+ remote: null
+ };
+ o = _.mixin(defaults, o || {});
+ !o.datumTokenizer && $.error("datumTokenizer is required");
+ !o.queryTokenizer && $.error("queryTokenizer is required");
+ sorter = o.sorter;
+ o.sorter = sorter ? function(x) {
+ return x.sort(sorter);
+ } : _.identity;
+ o.local = _.isFunction(o.local) ? o.local() : o.local;
+ o.prefetch = parsePrefetch(o.prefetch);
+ o.remote = parseRemote(o.remote);
+ return o;
+ };
+ function parsePrefetch(o) {
+ var defaults;
+ if (!o) {
+ return null;
+ }
+ defaults = {
+ url: null,
+ ttl: 24 * 60 * 60 * 1e3,
+ cache: true,
+ cacheKey: null,
+ thumbprint: "",
+ prepare: _.identity,
+ transform: _.identity,
+ transport: null
+ };
+ o = _.isString(o) ? {
+ url: o
+ } : o;
+ o = _.mixin(defaults, o);
+ !o.url && $.error("prefetch requires url to be set");
+ o.transform = o.filter || o.transform;
+ o.cacheKey = o.cacheKey || o.url;
+ o.thumbprint = VERSION + o.thumbprint;
+ o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax;
+ return o;
+ }
+ function parseRemote(o) {
+ var defaults;
+ if (!o) {
+ return;
+ }
+ defaults = {
+ url: null,
+ cache: true,
+ prepare: null,
+ replace: null,
+ wildcard: null,
+ limiter: null,
+ rateLimitBy: "debounce",
+ rateLimitWait: 300,
+ transform: _.identity,
+ transport: null
+ };
+ o = _.isString(o) ? {
+ url: o
+ } : o;
+ o = _.mixin(defaults, o);
+ !o.url && $.error("remote requires url to be set");
+ o.transform = o.filter || o.transform;
+ o.prepare = toRemotePrepare(o);
+ o.limiter = toLimiter(o);
+ o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax;
+ delete o.replace;
+ delete o.wildcard;
+ delete o.rateLimitBy;
+ delete o.rateLimitWait;
+ return o;
+ }
+ function toRemotePrepare(o) {
+ var prepare, replace, wildcard;
+ prepare = o.prepare;
+ replace = o.replace;
+ wildcard = o.wildcard;
+ if (prepare) {
+ return prepare;
+ }
+ if (replace) {
+ prepare = prepareByReplace;
+ } else if (o.wildcard) {
+ prepare = prepareByWildcard;
+ } else {
+ prepare = idenityPrepare;
+ }
+ return prepare;
+ function prepareByReplace(query, settings) {
+ settings.url = replace(settings.url, query);
+ return settings;
+ }
+ function prepareByWildcard(query, settings) {
+ settings.url = settings.url.replace(wildcard, encodeURIComponent(query));
+ return settings;
+ }
+ function idenityPrepare(query, settings) {
+ return settings;
+ }
+ }
+ function toLimiter(o) {
+ var limiter, method, wait;
+ limiter = o.limiter;
+ method = o.rateLimitBy;
+ wait = o.rateLimitWait;
+ if (!limiter) {
+ limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait);
+ }
+ return limiter;
+ function debounce(wait) {
+ return function debounce(fn) {
+ return _.debounce(fn, wait);
+ };
+ }
+ function throttle(wait) {
+ return function throttle(fn) {
+ return _.throttle(fn, wait);
+ };
+ }
+ }
+ function callbackToDeferred(fn) {
+ return function wrapper(o) {
+ var deferred = $.Deferred();
+ fn(o, onSuccess, onError);
+ return deferred;
+ function onSuccess(resp) {
+ _.defer(function() {
+ deferred.resolve(resp);
+ });
+ }
+ function onError(err) {
+ _.defer(function() {
+ deferred.reject(err);
+ });
+ }
+ };
+ }
+ }();
+ var Bloodhound = function() {
+ "use strict";
+ var old;
+ old = window && window.Bloodhound;
+ function Bloodhound(o) {
+ o = oParser(o);
+ this.sorter = o.sorter;
+ this.identify = o.identify;
+ this.sufficient = o.sufficient;
+ this.local = o.local;
+ this.remote = o.remote ? new Remote(o.remote) : null;
+ this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null;
+ this.index = new SearchIndex({
+ identify: this.identify,
+ datumTokenizer: o.datumTokenizer,
+ queryTokenizer: o.queryTokenizer
+ });
+ o.initialize !== false && this.initialize();
+ }
+ Bloodhound.noConflict = function noConflict() {
+ window && (window.Bloodhound = old);
+ return Bloodhound;
+ };
+ Bloodhound.tokenizers = tokenizers;
+ _.mixin(Bloodhound.prototype, {
+ __ttAdapter: function ttAdapter() {
+ var that = this;
+ return this.remote ? withAsync : withoutAsync;
+ function withAsync(query, sync, async) {
+ return that.search(query, sync, async);
+ }
+ function withoutAsync(query, sync) {
+ return that.search(query, sync);
+ }
+ },
+ _loadPrefetch: function loadPrefetch() {
+ var that = this, deferred, serialized;
+ deferred = $.Deferred();
+ if (!this.prefetch) {
+ deferred.resolve();
+ } else if (serialized = this.prefetch.fromCache()) {
+ this.index.bootstrap(serialized);
+ deferred.resolve();
+ } else {
+ this.prefetch.fromNetwork(done);
+ }
+ return deferred.promise();
+ function done(err, data) {
+ if (err) {
+ return deferred.reject();
+ }
+ that.add(data);
+ that.prefetch.store(that.index.serialize());
+ deferred.resolve();
+ }
+ },
+ _initialize: function initialize() {
+ var that = this, deferred;
+ this.clear();
+ (this.initPromise = this._loadPrefetch()).done(addLocalToIndex);
+ return this.initPromise;
+ function addLocalToIndex() {
+ that.add(that.local);
+ }
+ },
+ initialize: function initialize(force) {
+ return !this.initPromise || force ? this._initialize() : this.initPromise;
+ },
+ add: function add(data) {
+ this.index.add(data);
+ return this;
+ },
+ get: function get(ids) {
+ ids = _.isArray(ids) ? ids : [].slice.call(arguments);
+ return this.index.get(ids);
+ },
+ search: function search(query, sync, async) {
+ var that = this, local;
+ local = this.sorter(this.index.search(query));
+ sync(this.remote ? local.slice() : local);
+ if (this.remote && local.length < this.sufficient) {
+ this.remote.get(query, processRemote);
+ } else if (this.remote) {
+ this.remote.cancelLastRequest();
+ }
+ return this;
+ function processRemote(remote) {
+ var nonDuplicates = [];
+ _.each(remote, function(r) {
+ !_.some(local, function(l) {
+ return that.identify(r) === that.identify(l);
+ }) && nonDuplicates.push(r);
+ });
+ async && async(nonDuplicates);
+ }
+ },
+ all: function all() {
+ return this.index.all();
+ },
+ clear: function clear() {
+ this.index.reset();
+ return this;
+ },
+ clearPrefetchCache: function clearPrefetchCache() {
+ this.prefetch && this.prefetch.clear();
+ return this;
+ },
+ clearRemoteCache: function clearRemoteCache() {
+ Transport.resetCache();
+ return this;
+ },
+ ttAdapter: function ttAdapter() {
+ return this.__ttAdapter();
+ }
+ });
+ return Bloodhound;
+ }();
+ return Bloodhound;
+});
+
+(function(root, factory) {
+ if (typeof define === "function" && define.amd) {
+ define("typeahead.js", [ "jquery" ], function(a0) {
+ return factory(a0);
+ });
+ } else if (typeof exports === "object") {
+ module.exports = factory(require("jquery"));
+ } else {
+ factory(jQuery);
+ }
+})(this, function($) {
+ var _ = function() {
+ "use strict";
+ return {
+ isMsie: function() {
+ return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
+ },
+ isBlankString: function(str) {
+ return !str || /^\s*$/.test(str);
+ },
+ escapeRegExChars: function(str) {
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+ },
+ isString: function(obj) {
+ return typeof obj === "string";
+ },
+ isNumber: function(obj) {
+ return typeof obj === "number";
+ },
+ isArray: $.isArray,
+ isFunction: $.isFunction,
+ isObject: $.isPlainObject,
+ isUndefined: function(obj) {
+ return typeof obj === "undefined";
+ },
+ isElement: function(obj) {
+ return !!(obj && obj.nodeType === 1);
+ },
+ isJQuery: function(obj) {
+ return obj instanceof $;
+ },
+ toStr: function toStr(s) {
+ return _.isUndefined(s) || s === null ? "" : s + "";
+ },
+ bind: $.proxy,
+ each: function(collection, cb) {
+ $.each(collection, reverseArgs);
+ function reverseArgs(index, value) {
+ return cb(value, index);
+ }
+ },
+ map: $.map,
+ filter: $.grep,
+ every: function(obj, test) {
+ var result = true;
+ if (!obj) {
+ return result;
+ }
+ $.each(obj, function(key, val) {
+ if (!(result = test.call(null, val, key, obj))) {
+ return false;
+ }
+ });
+ return !!result;
+ },
+ some: function(obj, test) {
+ var result = false;
+ if (!obj) {
+ return result;
+ }
+ $.each(obj, function(key, val) {
+ if (result = test.call(null, val, key, obj)) {
+ return false;
+ }
+ });
+ return !!result;
+ },
+ mixin: $.extend,
+ identity: function(x) {
+ return x;
+ },
+ clone: function(obj) {
+ return $.extend(true, {}, obj);
+ },
+ getIdGenerator: function() {
+ var counter = 0;
+ return function() {
+ return counter++;
+ };
+ },
+ templatify: function templatify(obj) {
+ return $.isFunction(obj) ? obj : template;
+ function template() {
+ return String(obj);
+ }
+ },
+ defer: function(fn) {
+ setTimeout(fn, 0);
+ },
+ debounce: function(func, wait, immediate) {
+ var timeout, result;
+ return function() {
+ var context = this, args = arguments, later, callNow;
+ later = function() {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ }
+ };
+ callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if (callNow) {
+ result = func.apply(context, args);
+ }
+ return result;
+ };
+ },
+ throttle: function(func, wait) {
+ var context, args, timeout, result, previous, later;
+ previous = 0;
+ later = function() {
+ previous = new Date();
+ timeout = null;
+ result = func.apply(context, args);
+ };
+ return function() {
+ var now = new Date(), remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(context, args);
+ } else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+ },
+ stringify: function(val) {
+ return _.isString(val) ? val : JSON.stringify(val);
+ },
+ noop: function() {}
+ };
+ }();
+ var WWW = function() {
+ "use strict";
+ var defaultClassNames = {
+ wrapper: "twitter-typeahead",
+ input: "tt-input",
+ hint: "tt-hint",
+ menu: "tt-menu",
+ dataset: "tt-dataset",
+ suggestion: "tt-suggestion",
+ selectable: "tt-selectable",
+ empty: "tt-empty",
+ open: "tt-open",
+ cursor: "tt-cursor",
+ highlight: "tt-highlight"
+ };
+ return build;
+ function build(o) {
+ var www, classes;
+ classes = _.mixin({}, defaultClassNames, o);
+ www = {
+ css: buildCss(),
+ classes: classes,
+ html: buildHtml(classes),
+ selectors: buildSelectors(classes)
+ };
+ return {
+ css: www.css,
+ html: www.html,
+ classes: www.classes,
+ selectors: www.selectors,
+ mixin: function(o) {
+ _.mixin(o, www);
+ }
+ };
+ }
+ function buildHtml(c) {
+ return {
+ wrapper: ' ',
+ menu: ''
+ };
+ }
+ function buildSelectors(classes) {
+ var selectors = {};
+ _.each(classes, function(v, k) {
+ selectors[k] = "." + v;
+ });
+ return selectors;
+ }
+ function buildCss() {
+ var css = {
+ wrapper: {
+ position: "relative",
+ display: "inline-block"
+ },
+ hint: {
+ position: "absolute",
+ top: "0",
+ left: "0",
+ borderColor: "transparent",
+ boxShadow: "none",
+ opacity: "1"
+ },
+ input: {
+ position: "relative",
+ verticalAlign: "top",
+ backgroundColor: "transparent"
+ },
+ inputWithNoHint: {
+ position: "relative",
+ verticalAlign: "top"
+ },
+ menu: {
+ position: "absolute",
+ top: "100%",
+ left: "0",
+ zIndex: "100",
+ display: "none"
+ },
+ ltr: {
+ left: "0",
+ right: "auto"
+ },
+ rtl: {
+ left: "auto",
+ right: " 0"
+ }
+ };
+ if (_.isMsie()) {
+ _.mixin(css.input, {
+ backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
+ });
+ }
+ return css;
+ }
+ }();
+ var EventBus = function() {
+ "use strict";
+ var namespace, deprecationMap;
+ namespace = "typeahead:";
+ deprecationMap = {
+ render: "rendered",
+ cursorchange: "cursorchanged",
+ select: "selected",
+ autocomplete: "autocompleted"
+ };
+ function EventBus(o) {
+ if (!o || !o.el) {
+ $.error("EventBus initialized without el");
+ }
+ this.$el = $(o.el);
+ }
+ _.mixin(EventBus.prototype, {
+ _trigger: function(type, args) {
+ var $e;
+ $e = $.Event(namespace + type);
+ (args = args || []).unshift($e);
+ this.$el.trigger.apply(this.$el, args);
+ return $e;
+ },
+ before: function(type) {
+ var args, $e;
+ args = [].slice.call(arguments, 1);
+ $e = this._trigger("before" + type, args);
+ return $e.isDefaultPrevented();
+ },
+ trigger: function(type) {
+ var deprecatedType;
+ this._trigger(type, [].slice.call(arguments, 1));
+ if (deprecatedType = deprecationMap[type]) {
+ this._trigger(deprecatedType, [].slice.call(arguments, 1));
+ }
+ }
+ });
+ return EventBus;
+ }();
+ var EventEmitter = function() {
+ "use strict";
+ var splitter = /\s+/, nextTick = getNextTick();
+ return {
+ onSync: onSync,
+ onAsync: onAsync,
+ off: off,
+ trigger: trigger
+ };
+ function on(method, types, cb, context) {
+ var type;
+ if (!cb) {
+ return this;
+ }
+ types = types.split(splitter);
+ cb = context ? bindContext(cb, context) : cb;
+ this._callbacks = this._callbacks || {};
+ while (type = types.shift()) {
+ this._callbacks[type] = this._callbacks[type] || {
+ sync: [],
+ async: []
+ };
+ this._callbacks[type][method].push(cb);
+ }
+ return this;
+ }
+ function onAsync(types, cb, context) {
+ return on.call(this, "async", types, cb, context);
+ }
+ function onSync(types, cb, context) {
+ return on.call(this, "sync", types, cb, context);
+ }
+ function off(types) {
+ var type;
+ if (!this._callbacks) {
+ return this;
+ }
+ types = types.split(splitter);
+ while (type = types.shift()) {
+ delete this._callbacks[type];
+ }
+ return this;
+ }
+ function trigger(types) {
+ var type, callbacks, args, syncFlush, asyncFlush;
+ if (!this._callbacks) {
+ return this;
+ }
+ types = types.split(splitter);
+ args = [].slice.call(arguments, 1);
+ while ((type = types.shift()) && (callbacks = this._callbacks[type])) {
+ syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args));
+ asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args));
+ syncFlush() && nextTick(asyncFlush);
+ }
+ return this;
+ }
+ function getFlush(callbacks, context, args) {
+ return flush;
+ function flush() {
+ var cancelled;
+ for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) {
+ cancelled = callbacks[i].apply(context, args) === false;
+ }
+ return !cancelled;
+ }
+ }
+ function getNextTick() {
+ var nextTickFn;
+ if (window.setImmediate) {
+ nextTickFn = function nextTickSetImmediate(fn) {
+ setImmediate(function() {
+ fn();
+ });
+ };
+ } else {
+ nextTickFn = function nextTickSetTimeout(fn) {
+ setTimeout(function() {
+ fn();
+ }, 0);
+ };
+ }
+ return nextTickFn;
+ }
+ function bindContext(fn, context) {
+ return fn.bind ? fn.bind(context) : function() {
+ fn.apply(context, [].slice.call(arguments, 0));
+ };
+ }
+ }();
+ var highlight = function(doc) {
+ "use strict";
+ var defaults = {
+ node: null,
+ pattern: null,
+ tagName: "strong",
+ className: null,
+ wordsOnly: false,
+ caseSensitive: false
+ };
+ return function hightlight(o) {
+ var regex;
+ o = _.mixin({}, defaults, o);
+ if (!o.node || !o.pattern) {
+ return;
+ }
+ o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];
+ regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
+ traverse(o.node, hightlightTextNode);
+ function hightlightTextNode(textNode) {
+ var match, patternNode, wrapperNode;
+ if (match = regex.exec(textNode.data)) {
+ wrapperNode = doc.createElement(o.tagName);
+ o.className && (wrapperNode.className = o.className);
+ patternNode = textNode.splitText(match.index);
+ patternNode.splitText(match[0].length);
+ wrapperNode.appendChild(patternNode.cloneNode(true));
+ textNode.parentNode.replaceChild(wrapperNode, patternNode);
+ }
+ return !!match;
+ }
+ function traverse(el, hightlightTextNode) {
+ var childNode, TEXT_NODE_TYPE = 3;
+ for (var i = 0; i < el.childNodes.length; i++) {
+ childNode = el.childNodes[i];
+ if (childNode.nodeType === TEXT_NODE_TYPE) {
+ i += hightlightTextNode(childNode) ? 1 : 0;
+ } else {
+ traverse(childNode, hightlightTextNode);
+ }
+ }
+ }
+ };
+ function getRegex(patterns, caseSensitive, wordsOnly) {
+ var escapedPatterns = [], regexStr;
+ for (var i = 0, len = patterns.length; i < len; i++) {
+ escapedPatterns.push(_.escapeRegExChars(patterns[i]));
+ }
+ regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
+ return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i");
+ }
+ }(window.document);
+ var Input = function() {
+ "use strict";
+ var specialKeyCodeMap;
+ specialKeyCodeMap = {
+ 9: "tab",
+ 27: "esc",
+ 37: "left",
+ 39: "right",
+ 13: "enter",
+ 38: "up",
+ 40: "down"
+ };
+ function Input(o, www) {
+ o = o || {};
+ if (!o.input) {
+ $.error("input is missing");
+ }
+ www.mixin(this);
+ this.$hint = $(o.hint);
+ this.$input = $(o.input);
+ this.query = this.$input.val();
+ this.queryWhenFocused = this.hasFocus() ? this.query : null;
+ this.$overflowHelper = buildOverflowHelper(this.$input);
+ this._checkLanguageDirection();
+ if (this.$hint.length === 0) {
+ this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
+ }
+ }
+ Input.normalizeQuery = function(str) {
+ return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
+ };
+ _.mixin(Input.prototype, EventEmitter, {
+ _onBlur: function onBlur() {
+ this.resetInputValue();
+ this.trigger("blurred");
+ },
+ _onFocus: function onFocus() {
+ this.queryWhenFocused = this.query;
+ this.trigger("focused");
+ },
+ _onKeydown: function onKeydown($e) {
+ var keyName = specialKeyCodeMap[$e.which || $e.keyCode];
+ this._managePreventDefault(keyName, $e);
+ if (keyName && this._shouldTrigger(keyName, $e)) {
+ this.trigger(keyName + "Keyed", $e);
+ }
+ },
+ _onInput: function onInput() {
+ this._setQuery(this.getInputValue());
+ this.clearHintIfInvalid();
+ this._checkLanguageDirection();
+ },
+ _managePreventDefault: function managePreventDefault(keyName, $e) {
+ var preventDefault;
+ switch (keyName) {
+ case "up":
+ case "down":
+ preventDefault = !withModifier($e);
+ break;
+
+ default:
+ preventDefault = false;
+ }
+ preventDefault && $e.preventDefault();
+ },
+ _shouldTrigger: function shouldTrigger(keyName, $e) {
+ var trigger;
+ switch (keyName) {
+ case "tab":
+ trigger = !withModifier($e);
+ break;
+
+ default:
+ trigger = true;
+ }
+ return trigger;
+ },
+ _checkLanguageDirection: function checkLanguageDirection() {
+ var dir = (this.$input.css("direction") || "ltr").toLowerCase();
+ if (this.dir !== dir) {
+ this.dir = dir;
+ this.$hint.attr("dir", dir);
+ this.trigger("langDirChanged", dir);
+ }
+ },
+ _setQuery: function setQuery(val, silent) {
+ var areEquivalent, hasDifferentWhitespace;
+ areEquivalent = areQueriesEquivalent(val, this.query);
+ hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false;
+ this.query = val;
+ if (!silent && !areEquivalent) {
+ this.trigger("queryChanged", this.query);
+ } else if (!silent && hasDifferentWhitespace) {
+ this.trigger("whitespaceChanged", this.query);
+ }
+ },
+ bind: function() {
+ var that = this, onBlur, onFocus, onKeydown, onInput;
+ onBlur = _.bind(this._onBlur, this);
+ onFocus = _.bind(this._onFocus, this);
+ onKeydown = _.bind(this._onKeydown, this);
+ onInput = _.bind(this._onInput, this);
+ this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
+ if (!_.isMsie() || _.isMsie() > 9) {
+ this.$input.on("input.tt", onInput);
+ } else {
+ this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
+ if (specialKeyCodeMap[$e.which || $e.keyCode]) {
+ return;
+ }
+ _.defer(_.bind(that._onInput, that, $e));
+ });
+ }
+ return this;
+ },
+ focus: function focus() {
+ this.$input.focus();
+ },
+ blur: function blur() {
+ this.$input.blur();
+ },
+ getLangDir: function getLangDir() {
+ return this.dir;
+ },
+ getQuery: function getQuery() {
+ return this.query || "";
+ },
+ setQuery: function setQuery(val, silent) {
+ this.setInputValue(val);
+ this._setQuery(val, silent);
+ },
+ hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() {
+ return this.query !== this.queryWhenFocused;
+ },
+ getInputValue: function getInputValue() {
+ return this.$input.val();
+ },
+ setInputValue: function setInputValue(value) {
+ this.$input.val(value);
+ this.clearHintIfInvalid();
+ this._checkLanguageDirection();
+ },
+ resetInputValue: function resetInputValue() {
+ this.setInputValue(this.query);
+ },
+ getHint: function getHint() {
+ return this.$hint.val();
+ },
+ setHint: function setHint(value) {
+ this.$hint.val(value);
+ },
+ clearHint: function clearHint() {
+ this.setHint("");
+ },
+ clearHintIfInvalid: function clearHintIfInvalid() {
+ var val, hint, valIsPrefixOfHint, isValid;
+ val = this.getInputValue();
+ hint = this.getHint();
+ valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;
+ isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow();
+ !isValid && this.clearHint();
+ },
+ hasFocus: function hasFocus() {
+ return this.$input.is(":focus");
+ },
+ hasOverflow: function hasOverflow() {
+ var constraint = this.$input.width() - 2;
+ this.$overflowHelper.text(this.getInputValue());
+ return this.$overflowHelper.width() >= constraint;
+ },
+ isCursorAtEnd: function() {
+ var valueLength, selectionStart, range;
+ valueLength = this.$input.val().length;
+ selectionStart = this.$input[0].selectionStart;
+ if (_.isNumber(selectionStart)) {
+ return selectionStart === valueLength;
+ } else if (document.selection) {
+ range = document.selection.createRange();
+ range.moveStart("character", -valueLength);
+ return valueLength === range.text.length;
+ }
+ return true;
+ },
+ destroy: function destroy() {
+ this.$hint.off(".tt");
+ this.$input.off(".tt");
+ this.$overflowHelper.remove();
+ this.$hint = this.$input = this.$overflowHelper = $("");
+ }
+ });
+ return Input;
+ function buildOverflowHelper($input) {
+ return $('
').css({
+ position: "absolute",
+ visibility: "hidden",
+ whiteSpace: "pre",
+ fontFamily: $input.css("font-family"),
+ fontSize: $input.css("font-size"),
+ fontStyle: $input.css("font-style"),
+ fontVariant: $input.css("font-variant"),
+ fontWeight: $input.css("font-weight"),
+ wordSpacing: $input.css("word-spacing"),
+ letterSpacing: $input.css("letter-spacing"),
+ textIndent: $input.css("text-indent"),
+ textRendering: $input.css("text-rendering"),
+ textTransform: $input.css("text-transform")
+ }).insertAfter($input);
+ }
+ function areQueriesEquivalent(a, b) {
+ return Input.normalizeQuery(a) === Input.normalizeQuery(b);
+ }
+ function withModifier($e) {
+ return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey;
+ }
+ }();
+ var Dataset = function() {
+ "use strict";
+ var keys, nameGenerator;
+ keys = {
+ val: "tt-selectable-display",
+ obj: "tt-selectable-object"
+ };
+ nameGenerator = _.getIdGenerator();
+ function Dataset(o, www) {
+ o = o || {};
+ o.templates = o.templates || {};
+ o.templates.notFound = o.templates.notFound || o.templates.empty;
+ if (!o.source) {
+ $.error("missing source");
+ }
+ if (!o.node) {
+ $.error("missing node");
+ }
+ if (o.name && !isValidName(o.name)) {
+ $.error("invalid dataset name: " + o.name);
+ }
+ www.mixin(this);
+ this.highlight = !!o.highlight;
+ this.name = o.name || nameGenerator();
+ this.limit = o.limit || 5;
+ this.displayFn = getDisplayFn(o.display || o.displayKey);
+ this.templates = getTemplates(o.templates, this.displayFn);
+ this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source;
+ this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async;
+ this._resetLastSuggestion();
+ this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name);
+ }
+ Dataset.extractData = function extractData(el) {
+ var $el = $(el);
+ if ($el.data(keys.obj)) {
+ return {
+ val: $el.data(keys.val) || "",
+ obj: $el.data(keys.obj) || null
+ };
+ }
+ return null;
+ };
+ _.mixin(Dataset.prototype, EventEmitter, {
+ _overwrite: function overwrite(query, suggestions) {
+ suggestions = suggestions || [];
+ if (suggestions.length) {
+ this._renderSuggestions(query, suggestions);
+ } else if (this.async && this.templates.pending) {
+ this._renderPending(query);
+ } else if (!this.async && this.templates.notFound) {
+ this._renderNotFound(query);
+ } else {
+ this._empty();
+ }
+ this.trigger("rendered", this.name, suggestions, false);
+ },
+ _append: function append(query, suggestions) {
+ suggestions = suggestions || [];
+ if (suggestions.length && this.$lastSuggestion.length) {
+ this._appendSuggestions(query, suggestions);
+ } else if (suggestions.length) {
+ this._renderSuggestions(query, suggestions);
+ } else if (!this.$lastSuggestion.length && this.templates.notFound) {
+ this._renderNotFound(query);
+ }
+ this.trigger("rendered", this.name, suggestions, true);
+ },
+ _renderSuggestions: function renderSuggestions(query, suggestions) {
+ var $fragment;
+ $fragment = this._getSuggestionsFragment(query, suggestions);
+ this.$lastSuggestion = $fragment.children().last();
+ this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions));
+ },
+ _appendSuggestions: function appendSuggestions(query, suggestions) {
+ var $fragment, $lastSuggestion;
+ $fragment = this._getSuggestionsFragment(query, suggestions);
+ $lastSuggestion = $fragment.children().last();
+ this.$lastSuggestion.after($fragment);
+ this.$lastSuggestion = $lastSuggestion;
+ },
+ _renderPending: function renderPending(query) {
+ var template = this.templates.pending;
+ this._resetLastSuggestion();
+ template && this.$el.html(template({
+ query: query,
+ dataset: this.name
+ }));
+ },
+ _renderNotFound: function renderNotFound(query) {
+ var template = this.templates.notFound;
+ this._resetLastSuggestion();
+ template && this.$el.html(template({
+ query: query,
+ dataset: this.name
+ }));
+ },
+ _empty: function empty() {
+ this.$el.empty();
+ this._resetLastSuggestion();
+ },
+ _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) {
+ var that = this, fragment;
+ fragment = document.createDocumentFragment();
+ _.each(suggestions, function getSuggestionNode(suggestion) {
+ var $el, context;
+ context = that._injectQuery(query, suggestion);
+ $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable);
+ fragment.appendChild($el[0]);
+ });
+ this.highlight && highlight({
+ className: this.classes.highlight,
+ node: fragment,
+ pattern: query
+ });
+ return $(fragment);
+ },
+ _getFooter: function getFooter(query, suggestions) {
+ return this.templates.footer ? this.templates.footer({
+ query: query,
+ suggestions: suggestions,
+ dataset: this.name
+ }) : null;
+ },
+ _getHeader: function getHeader(query, suggestions) {
+ return this.templates.header ? this.templates.header({
+ query: query,
+ suggestions: suggestions,
+ dataset: this.name
+ }) : null;
+ },
+ _resetLastSuggestion: function resetLastSuggestion() {
+ this.$lastSuggestion = $();
+ },
+ _injectQuery: function injectQuery(query, obj) {
+ return _.isObject(obj) ? _.mixin({
+ _query: query
+ }, obj) : obj;
+ },
+ update: function update(query) {
+ var that = this, canceled = false, syncCalled = false, rendered = 0;
+ this.cancel();
+ this.cancel = function cancel() {
+ canceled = true;
+ that.cancel = $.noop;
+ that.async && that.trigger("asyncCanceled", query);
+ };
+ this.source(query, sync, async);
+ !syncCalled && sync([]);
+ function sync(suggestions) {
+ if (syncCalled) {
+ return;
+ }
+ syncCalled = true;
+ suggestions = (suggestions || []).slice(0, that.limit);
+ rendered = suggestions.length;
+ that._overwrite(query, suggestions);
+ if (rendered < that.limit && that.async) {
+ that.trigger("asyncRequested", query);
+ }
+ }
+ function async(suggestions) {
+ suggestions = suggestions || [];
+ if (!canceled && rendered < that.limit) {
+ that.cancel = $.noop;
+ rendered += suggestions.length;
+ that._append(query, suggestions.slice(0, that.limit - rendered));
+ that.async && that.trigger("asyncReceived", query);
+ }
+ }
+ },
+ cancel: $.noop,
+ clear: function clear() {
+ this._empty();
+ this.cancel();
+ this.trigger("cleared");
+ },
+ isEmpty: function isEmpty() {
+ return this.$el.is(":empty");
+ },
+ destroy: function destroy() {
+ this.$el = $("
");
+ }
+ });
+ return Dataset;
+ function getDisplayFn(display) {
+ display = display || _.stringify;
+ return _.isFunction(display) ? display : displayFn;
+ function displayFn(obj) {
+ return obj[display];
+ }
+ }
+ function getTemplates(templates, displayFn) {
+ return {
+ notFound: templates.notFound && _.templatify(templates.notFound),
+ pending: templates.pending && _.templatify(templates.pending),
+ header: templates.header && _.templatify(templates.header),
+ footer: templates.footer && _.templatify(templates.footer),
+ suggestion: templates.suggestion || suggestionTemplate
+ };
+ function suggestionTemplate(context) {
+ return $("
").text(displayFn(context));
+ }
+ }
+ function isValidName(str) {
+ return /^[_a-zA-Z0-9-]+$/.test(str);
+ }
+ }();
+ var Menu = function() {
+ "use strict";
+ function Menu(o, www) {
+ var that = this;
+ o = o || {};
+ if (!o.node) {
+ $.error("node is required");
+ }
+ www.mixin(this);
+ this.$node = $(o.node);
+ this.query = null;
+ this.datasets = _.map(o.datasets, initializeDataset);
+ function initializeDataset(oDataset) {
+ var node = that.$node.find(oDataset.node).first();
+ oDataset.node = node.length ? node : $("
").appendTo(that.$node);
+ return new Dataset(oDataset, www);
+ }
+ }
+ _.mixin(Menu.prototype, EventEmitter, {
+ _onSelectableClick: function onSelectableClick($e) {
+ this.trigger("selectableClicked", $($e.currentTarget));
+ },
+ _onRendered: function onRendered(type, dataset, suggestions, async) {
+ this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
+ this.trigger("datasetRendered", dataset, suggestions, async);
+ },
+ _onCleared: function onCleared() {
+ this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
+ this.trigger("datasetCleared");
+ },
+ _propagate: function propagate() {
+ this.trigger.apply(this, arguments);
+ },
+ _allDatasetsEmpty: function allDatasetsEmpty() {
+ return _.every(this.datasets, isDatasetEmpty);
+ function isDatasetEmpty(dataset) {
+ return dataset.isEmpty();
+ }
+ },
+ _getSelectables: function getSelectables() {
+ return this.$node.find(this.selectors.selectable);
+ },
+ _removeCursor: function _removeCursor() {
+ var $selectable = this.getActiveSelectable();
+ $selectable && $selectable.removeClass(this.classes.cursor);
+ },
+ _ensureVisible: function ensureVisible($el) {
+ var elTop, elBottom, nodeScrollTop, nodeHeight;
+ elTop = $el.position().top;
+ elBottom = elTop + $el.outerHeight(true);
+ nodeScrollTop = this.$node.scrollTop();
+ nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10);
+ if (elTop < 0) {
+ this.$node.scrollTop(nodeScrollTop + elTop);
+ } else if (nodeHeight < elBottom) {
+ this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight));
+ }
+ },
+ bind: function() {
+ var that = this, onSelectableClick;
+ onSelectableClick = _.bind(this._onSelectableClick, this);
+ this.$node.on("click.tt", this.selectors.selectable, onSelectableClick);
+ _.each(this.datasets, function(dataset) {
+ dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that);
+ });
+ return this;
+ },
+ isOpen: function isOpen() {
+ return this.$node.hasClass(this.classes.open);
+ },
+ open: function open() {
+ this.$node.addClass(this.classes.open);
+ },
+ close: function close() {
+ this.$node.removeClass(this.classes.open);
+ this._removeCursor();
+ },
+ setLanguageDirection: function setLanguageDirection(dir) {
+ this.$node.attr("dir", dir);
+ },
+ selectableRelativeToCursor: function selectableRelativeToCursor(delta) {
+ var $selectables, $oldCursor, oldIndex, newIndex;
+ $oldCursor = this.getActiveSelectable();
+ $selectables = this._getSelectables();
+ oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1;
+ newIndex = oldIndex + delta;
+ newIndex = (newIndex + 1) % ($selectables.length + 1) - 1;
+ newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex;
+ return newIndex === -1 ? null : $selectables.eq(newIndex);
+ },
+ setCursor: function setCursor($selectable) {
+ this._removeCursor();
+ if ($selectable = $selectable && $selectable.first()) {
+ $selectable.addClass(this.classes.cursor);
+ this._ensureVisible($selectable);
+ }
+ },
+ getSelectableData: function getSelectableData($el) {
+ return $el && $el.length ? Dataset.extractData($el) : null;
+ },
+ getActiveSelectable: function getActiveSelectable() {
+ var $selectable = this._getSelectables().filter(this.selectors.cursor).first();
+ return $selectable.length ? $selectable : null;
+ },
+ getTopSelectable: function getTopSelectable() {
+ var $selectable = this._getSelectables().first();
+ return $selectable.length ? $selectable : null;
+ },
+ update: function update(query) {
+ var isValidUpdate = query !== this.query;
+ if (isValidUpdate) {
+ this.query = query;
+ _.each(this.datasets, updateDataset);
+ }
+ return isValidUpdate;
+ function updateDataset(dataset) {
+ dataset.update(query);
+ }
+ },
+ empty: function empty() {
+ _.each(this.datasets, clearDataset);
+ this.query = null;
+ this.$node.addClass(this.classes.empty);
+ function clearDataset(dataset) {
+ dataset.clear();
+ }
+ },
+ destroy: function destroy() {
+ this.$node.off(".tt");
+ this.$node = $("
");
+ _.each(this.datasets, destroyDataset);
+ function destroyDataset(dataset) {
+ dataset.destroy();
+ }
+ }
+ });
+ return Menu;
+ }();
+ var DefaultMenu = function() {
+ "use strict";
+ var s = Menu.prototype;
+ function DefaultMenu() {
+ Menu.apply(this, [].slice.call(arguments, 0));
+ }
+ _.mixin(DefaultMenu.prototype, Menu.prototype, {
+ open: function open() {
+ !this._allDatasetsEmpty() && this._show();
+ return s.open.apply(this, [].slice.call(arguments, 0));
+ },
+ close: function close() {
+ this._hide();
+ return s.close.apply(this, [].slice.call(arguments, 0));
+ },
+ _onRendered: function onRendered() {
+ if (this._allDatasetsEmpty()) {
+ this._hide();
+ } else {
+ this.isOpen() && this._show();
+ }
+ return s._onRendered.apply(this, [].slice.call(arguments, 0));
+ },
+ _onCleared: function onCleared() {
+ if (this._allDatasetsEmpty()) {
+ this._hide();
+ } else {
+ this.isOpen() && this._show();
+ }
+ return s._onCleared.apply(this, [].slice.call(arguments, 0));
+ },
+ setLanguageDirection: function setLanguageDirection(dir) {
+ this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl);
+ return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0));
+ },
+ _hide: function hide() {
+ this.$node.hide();
+ },
+ _show: function show() {
+ this.$node.css("display", "block");
+ }
+ });
+ return DefaultMenu;
+ }();
+ var Typeahead = function() {
+ "use strict";
+ function Typeahead(o, www) {
+ var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged;
+ o = o || {};
+ if (!o.input) {
+ $.error("missing input");
+ }
+ if (!o.menu) {
+ $.error("missing menu");
+ }
+ if (!o.eventBus) {
+ $.error("missing event bus");
+ }
+ www.mixin(this);
+ this.eventBus = o.eventBus;
+ this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
+ this.input = o.input;
+ this.menu = o.menu;
+ this.enabled = true;
+ this.active = false;
+ this.input.hasFocus() && this.activate();
+ this.dir = this.input.getLangDir();
+ this._hacks();
+ this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this);
+ onFocused = c(this, "activate", "open", "_onFocused");
+ onBlurred = c(this, "deactivate", "_onBlurred");
+ onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed");
+ onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed");
+ onEscKeyed = c(this, "isActive", "_onEscKeyed");
+ onUpKeyed = c(this, "isActive", "open", "_onUpKeyed");
+ onDownKeyed = c(this, "isActive", "open", "_onDownKeyed");
+ onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed");
+ onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed");
+ onQueryChanged = c(this, "_openIfActive", "_onQueryChanged");
+ onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged");
+ this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this);
+ }
+ _.mixin(Typeahead.prototype, {
+ _hacks: function hacks() {
+ var $input, $menu;
+ $input = this.input.$input || $("
");
+ $menu = this.menu.$node || $("
");
+ $input.on("blur.tt", function($e) {
+ var active, isActive, hasActive;
+ active = document.activeElement;
+ isActive = $menu.is(active);
+ hasActive = $menu.has(active).length > 0;
+ if (_.isMsie() && (isActive || hasActive)) {
+ $e.preventDefault();
+ $e.stopImmediatePropagation();
+ _.defer(function() {
+ $input.focus();
+ });
+ }
+ });
+ $menu.on("mousedown.tt", function($e) {
+ $e.preventDefault();
+ });
+ },
+ _onSelectableClicked: function onSelectableClicked(type, $el) {
+ this.select($el);
+ },
+ _onDatasetCleared: function onDatasetCleared() {
+ this._updateHint();
+ },
+ _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) {
+ this._updateHint();
+ this.eventBus.trigger("render", suggestions, async, dataset);
+ },
+ _onAsyncRequested: function onAsyncRequested(type, dataset, query) {
+ this.eventBus.trigger("asyncrequest", query, dataset);
+ },
+ _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) {
+ this.eventBus.trigger("asynccancel", query, dataset);
+ },
+ _onAsyncReceived: function onAsyncReceived(type, dataset, query) {
+ this.eventBus.trigger("asyncreceive", query, dataset);
+ },
+ _onFocused: function onFocused() {
+ this._minLengthMet() && this.menu.update(this.input.getQuery());
+ },
+ _onBlurred: function onBlurred() {
+ if (this.input.hasQueryChangedSinceLastFocus()) {
+ this.eventBus.trigger("change", this.input.getQuery());
+ }
+ },
+ _onEnterKeyed: function onEnterKeyed(type, $e) {
+ var $selectable;
+ if ($selectable = this.menu.getActiveSelectable()) {
+ this.select($selectable) && $e.preventDefault();
+ }
+ },
+ _onTabKeyed: function onTabKeyed(type, $e) {
+ var $selectable;
+ if ($selectable = this.menu.getActiveSelectable()) {
+ this.select($selectable) && $e.preventDefault();
+ } else if ($selectable = this.menu.getTopSelectable()) {
+ this.autocomplete($selectable) && $e.preventDefault();
+ }
+ },
+ _onEscKeyed: function onEscKeyed() {
+ this.close();
+ },
+ _onUpKeyed: function onUpKeyed() {
+ this.moveCursor(-1);
+ },
+ _onDownKeyed: function onDownKeyed() {
+ this.moveCursor(+1);
+ },
+ _onLeftKeyed: function onLeftKeyed() {
+ if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
+ this.autocomplete(this.menu.getTopSelectable());
+ }
+ },
+ _onRightKeyed: function onRightKeyed() {
+ if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
+ this.autocomplete(this.menu.getTopSelectable());
+ }
+ },
+ _onQueryChanged: function onQueryChanged(e, query) {
+ this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty();
+ },
+ _onWhitespaceChanged: function onWhitespaceChanged() {
+ this._updateHint();
+ },
+ _onLangDirChanged: function onLangDirChanged(e, dir) {
+ if (this.dir !== dir) {
+ this.dir = dir;
+ this.menu.setLanguageDirection(dir);
+ }
+ },
+ _openIfActive: function openIfActive() {
+ this.isActive() && this.open();
+ },
+ _minLengthMet: function minLengthMet(query) {
+ query = _.isString(query) ? query : this.input.getQuery() || "";
+ return query.length >= this.minLength;
+ },
+ _updateHint: function updateHint() {
+ var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match;
+ $selectable = this.menu.getTopSelectable();
+ data = this.menu.getSelectableData($selectable);
+ val = this.input.getInputValue();
+ if (data && !_.isBlankString(val) && !this.input.hasOverflow()) {
+ query = Input.normalizeQuery(val);
+ escapedQuery = _.escapeRegExChars(query);
+ frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
+ match = frontMatchRegEx.exec(data.val);
+ match && this.input.setHint(val + match[1]);
+ } else {
+ this.input.clearHint();
+ }
+ },
+ isEnabled: function isEnabled() {
+ return this.enabled;
+ },
+ enable: function enable() {
+ this.enabled = true;
+ },
+ disable: function disable() {
+ this.enabled = false;
+ },
+ isActive: function isActive() {
+ return this.active;
+ },
+ activate: function activate() {
+ if (this.isActive()) {
+ return true;
+ } else if (!this.isEnabled() || this.eventBus.before("active")) {
+ return false;
+ } else {
+ this.active = true;
+ this.eventBus.trigger("active");
+ return true;
+ }
+ },
+ deactivate: function deactivate() {
+ if (!this.isActive()) {
+ return true;
+ } else if (this.eventBus.before("idle")) {
+ return false;
+ } else {
+ this.active = false;
+ this.close();
+ this.eventBus.trigger("idle");
+ return true;
+ }
+ },
+ isOpen: function isOpen() {
+ return this.menu.isOpen();
+ },
+ open: function open() {
+ if (!this.isOpen() && !this.eventBus.before("open")) {
+ this.menu.open();
+ this._updateHint();
+ this.eventBus.trigger("open");
+ }
+ return this.isOpen();
+ },
+ close: function close() {
+ if (this.isOpen() && !this.eventBus.before("close")) {
+ this.menu.close();
+ this.input.clearHint();
+ this.input.resetInputValue();
+ this.eventBus.trigger("close");
+ }
+ return !this.isOpen();
+ },
+ setVal: function setVal(val) {
+ this.input.setQuery(_.toStr(val));
+ },
+ getVal: function getVal() {
+ return this.input.getQuery();
+ },
+ select: function select($selectable) {
+ var data = this.menu.getSelectableData($selectable);
+ if (data && !this.eventBus.before("select", data.obj)) {
+ this.input.setQuery(data.val, true);
+ this.eventBus.trigger("select", data.obj);
+ this.close();
+ return true;
+ }
+ return false;
+ },
+ autocomplete: function autocomplete($selectable) {
+ var query, data, isValid;
+ query = this.input.getQuery();
+ data = this.menu.getSelectableData($selectable);
+ isValid = data && query !== data.val;
+ if (isValid && !this.eventBus.before("autocomplete", data.obj)) {
+ this.input.setQuery(data.val);
+ this.eventBus.trigger("autocomplete", data.obj);
+ return true;
+ }
+ return false;
+ },
+ moveCursor: function moveCursor(delta) {
+ var query, $candidate, data, payload, cancelMove;
+ query = this.input.getQuery();
+ $candidate = this.menu.selectableRelativeToCursor(delta);
+ data = this.menu.getSelectableData($candidate);
+ payload = data ? data.obj : null;
+ cancelMove = this._minLengthMet() && this.menu.update(query);
+ if (!cancelMove && !this.eventBus.before("cursorchange", payload)) {
+ this.menu.setCursor($candidate);
+ if (data) {
+ this.input.setInputValue(data.val);
+ } else {
+ this.input.resetInputValue();
+ this._updateHint();
+ }
+ this.eventBus.trigger("cursorchange", payload);
+ return true;
+ }
+ return false;
+ },
+ destroy: function destroy() {
+ this.input.destroy();
+ this.menu.destroy();
+ }
+ });
+ return Typeahead;
+ function c(ctx) {
+ var methods = [].slice.call(arguments, 1);
+ return function() {
+ var args = [].slice.call(arguments);
+ _.each(methods, function(method) {
+ return ctx[method].apply(ctx, args);
+ });
+ };
+ }
+ }();
+ (function() {
+ "use strict";
+ var old, keys, methods;
+ old = $.fn.typeahead;
+ keys = {
+ www: "tt-www",
+ attrs: "tt-attrs",
+ typeahead: "tt-typeahead"
+ };
+ methods = {
+ initialize: function initialize(o, datasets) {
+ var www;
+ datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
+ o = o || {};
+ www = WWW(o.classNames);
+ return this.each(attach);
+ function attach() {
+ var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor;
+ _.each(datasets, function(d) {
+ d.highlight = !!o.highlight;
+ });
+ $input = $(this);
+ $wrapper = $(www.html.wrapper);
+ $hint = $elOrNull(o.hint);
+ $menu = $elOrNull(o.menu);
+ defaultHint = o.hint !== false && !$hint;
+ defaultMenu = o.menu !== false && !$menu;
+ defaultHint && ($hint = buildHintFromInput($input, www));
+ defaultMenu && ($menu = $(www.html.menu).css(www.css.menu));
+ $hint && $hint.val("");
+ $input = prepInput($input, www);
+ if (defaultHint || defaultMenu) {
+ $wrapper.css(www.css.wrapper);
+ $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint);
+ $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null);
+ }
+ MenuConstructor = defaultMenu ? DefaultMenu : Menu;
+ eventBus = new EventBus({
+ el: $input
+ });
+ input = new Input({
+ hint: $hint,
+ input: $input
+ }, www);
+ menu = new MenuConstructor({
+ node: $menu,
+ datasets: datasets
+ }, www);
+ typeahead = new Typeahead({
+ input: input,
+ menu: menu,
+ eventBus: eventBus,
+ minLength: o.minLength
+ }, www);
+ $input.data(keys.www, www);
+ $input.data(keys.typeahead, typeahead);
+ }
+ },
+ isEnabled: function isEnabled() {
+ var enabled;
+ ttEach(this.first(), function(t) {
+ enabled = t.isEnabled();
+ });
+ return enabled;
+ },
+ enable: function enable() {
+ ttEach(this, function(t) {
+ t.enable();
+ });
+ return this;
+ },
+ disable: function disable() {
+ ttEach(this, function(t) {
+ t.disable();
+ });
+ return this;
+ },
+ isActive: function isActive() {
+ var active;
+ ttEach(this.first(), function(t) {
+ active = t.isActive();
+ });
+ return active;
+ },
+ activate: function activate() {
+ ttEach(this, function(t) {
+ t.activate();
+ });
+ return this;
+ },
+ deactivate: function deactivate() {
+ ttEach(this, function(t) {
+ t.deactivate();
+ });
+ return this;
+ },
+ isOpen: function isOpen() {
+ var open;
+ ttEach(this.first(), function(t) {
+ open = t.isOpen();
+ });
+ return open;
+ },
+ open: function open() {
+ ttEach(this, function(t) {
+ t.open();
+ });
+ return this;
+ },
+ close: function close() {
+ ttEach(this, function(t) {
+ t.close();
+ });
+ return this;
+ },
+ select: function select(el) {
+ var success = false, $el = $(el);
+ ttEach(this.first(), function(t) {
+ success = t.select($el);
+ });
+ return success;
+ },
+ autocomplete: function autocomplete(el) {
+ var success = false, $el = $(el);
+ ttEach(this.first(), function(t) {
+ success = t.autocomplete($el);
+ });
+ return success;
+ },
+ moveCursor: function moveCursoe(delta) {
+ var success = false;
+ ttEach(this.first(), function(t) {
+ success = t.moveCursor(delta);
+ });
+ return success;
+ },
+ val: function val(newVal) {
+ var query;
+ if (!arguments.length) {
+ ttEach(this.first(), function(t) {
+ query = t.getVal();
+ });
+ return query;
+ } else {
+ ttEach(this, function(t) {
+ t.setVal(newVal);
+ });
+ return this;
+ }
+ },
+ destroy: function destroy() {
+ ttEach(this, function(typeahead, $input) {
+ revert($input);
+ typeahead.destroy();
+ });
+ return this;
+ }
+ };
+ $.fn.typeahead = function(method) {
+ if (methods[method]) {
+ return methods[method].apply(this, [].slice.call(arguments, 1));
+ } else {
+ return methods.initialize.apply(this, arguments);
+ }
+ };
+ $.fn.typeahead.noConflict = function noConflict() {
+ $.fn.typeahead = old;
+ return this;
+ };
+ function ttEach($els, fn) {
+ $els.each(function() {
+ var $input = $(this), typeahead;
+ (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input);
+ });
+ }
+ function buildHintFromInput($input, www) {
+ return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({
+ autocomplete: "off",
+ spellcheck: "false",
+ tabindex: -1
+ });
+ }
+ function prepInput($input, www) {
+ $input.data(keys.attrs, {
+ dir: $input.attr("dir"),
+ autocomplete: $input.attr("autocomplete"),
+ spellcheck: $input.attr("spellcheck"),
+ style: $input.attr("style")
+ });
+ $input.addClass(www.classes.input).attr({
+ autocomplete: "off",
+ spellcheck: false
+ });
+ try {
+ !$input.attr("dir") && $input.attr("dir", "auto");
+ } catch (e) {}
+ return $input;
+ }
+ function getBackgroundStyles($el) {
+ return {
+ backgroundAttachment: $el.css("background-attachment"),
+ backgroundClip: $el.css("background-clip"),
+ backgroundColor: $el.css("background-color"),
+ backgroundImage: $el.css("background-image"),
+ backgroundOrigin: $el.css("background-origin"),
+ backgroundPosition: $el.css("background-position"),
+ backgroundRepeat: $el.css("background-repeat"),
+ backgroundSize: $el.css("background-size")
+ };
+ }
+ function revert($input) {
+ var www, $wrapper;
+ www = $input.data(keys.www);
+ $wrapper = $input.parent().filter(www.selectors.wrapper);
+ _.each($input.data(keys.attrs), function(val, key) {
+ _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
+ });
+ $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input);
+ if ($wrapper.length) {
+ $input.detach().insertAfter($wrapper);
+ $wrapper.remove();
+ }
+ }
+ function $elOrNull(obj) {
+ var isValid, $el;
+ isValid = _.isJQuery(obj) || _.isElement(obj);
+ $el = isValid ? $(obj).first() : [];
+ return $el.length ? $el : null;
+ }
+ })();
+});
\ No newline at end of file
diff --git a/src/main/webapp/js/src/typeahead.bundle.min.i b/src/main/webapp/js/src/typeahead.bundle.min.i
new file mode 100644
index 0000000..abab8df
--- /dev/null
+++ b/src/main/webapp/js/src/typeahead.bundle.min.i
@@ -0,0 +1,734 @@
+
+
+
+
+
+
+
+
+
+
typeahead.js/typeahead.bundle.min.js at master · twitter/typeahead.js · GitHub
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Something went wrong with that request. Please try again.
+
+
+
+
+
+
+
+
+
+
You signed in with another tab or window. Reload to refresh your session.
+
You signed out in another tab or window. Reload to refresh your session.
+
+
+
+
diff --git a/src/main/webapp/js/tableEvents.js b/src/main/webapp/js/tableEvents.js
new file mode 100644
index 0000000..e9088a4
--- /dev/null
+++ b/src/main/webapp/js/tableEvents.js
@@ -0,0 +1,1267 @@
+function screenToTheLeft() {
+ var width = $('div#usersRequestsModal div.modal-body').width();
+ width = '' + width + 'px';
+ $('div#requestsAcceptanceContainer').animate({
+ left : width
+ }, '2500', function() {
+ $('div#requestsAcceptanceContainer').toggleClass('hideSection');
+ $('div#usersRequestsTableContainer').toggleClass('hideSection').css({
+ right : '0px'
+ });
+ $('table#usersRequestsTable').DataTable().columns.adjust().draw();
+ $('table#usersRequestsTable').DataTable().columns.adjust().responsive.recalc();
+ $('#usersRequestsTable th:first').removeClass('sorting_asc');
+ });
+ $('#userRequestsHeader').contents().first()[0].textContent = "Users' requests";
+ if (!$('button#sendAcceptance').hasClass('hideButton'))
+ $('button#sendAcceptance').toggleClass('hideButton');
+ if (!$('button#sendRejection').hasClass('hideButton'))
+ $('button#sendRejection').toggleClass('hideButton');
+ $('button#acceptAll').toggleClass('hideButton');
+ $('button#rejectAll').toggleClass('hideButton');
+}
+
+$('#usersRequestsModal #closeUsersRolesModal').off().on(
+ 'click',
+ function() {
+ if ($('#closeUsersRolesModal').data('btnData') !== 0) {
+ screenToTheLeft();
+ }
+
+ $('div#usersRequestsModal').modal('hide');
+
+ $('#userEditedMailTemplate').closest('div').replaceWith('');
+ if ($('#emailForRejection').length !== 1) {
+ $('div#requestsAcceptanceBody .row:last').append(
+ automaticRejectionEmailTemplate);
+ $('#editEmailTemplate').tooltip();
+ }
+ });
+
+//TODO
+$('#singleTag').off('click').on('click', function(){
+ deletePreviousRoles = $('#singleTag').prop('checked');
+
+ if(deletePreviousRoles){
+ $('#changeUsersRolesModal .row:not(:first) .text-tag').remove();
+ }
+});
+
+$('#singleTagInAssignUsersToGroupsModal').off('click').on('click', function(){
+ deletePreviousRoles = $('#singleTagInAssignUsersToGroupsModal').prop('checked');
+
+ if(deletePreviousRoles){
+ $('#assignUsersToGroupsModal .row:not(:first) .text-tag').remove();
+ }
+});
+
+$('#singleTagInAssignRolesModal').off('click').on('click', function(){
+ deletePreviousRoles = $('#singleTagInAssignRolesModal').prop('checked');
+
+ if(deletePreviousRoles){
+ $('#assignUsersRolesModal .row:not(:first) .text-tag').remove();
+ }
+});
+
+function tableEvents() {
+ //Press all column-title, select all, deselect all
+ $('table:not(#GroupTeamsTableUsers):not(#GroupTeamsTable):not(#rejectedUsersRequestsTable) thead th:first-of-type').off('click').on('click', function(){
+ var $table = $(this).closest('table');
+ var $rows = $table.find('tbody tr');
+ var rowsCount = $rows.length;
+ var rowsCellsCount = $rows.find('td').length;
+ //If 0 rows do nothing
+ if(rowsCellsCount > 1){
+ $(this).toggleClass('none');
+
+ if($(this).hasClass('none')){
+
+// $(this).find('div').text('none');
+// $(this).find('div').css('padding-left','0px');
+
+ $rows.addClass('selected');
+ $rows.find('.icon-ok').addClass('whiteFont');
+
+ usersTableDataForEditing = [];
+ userTableUUIDsForEditing = [];
+ var countSelected = $('table#CurrentUsersTable tr.selected').length;
+ var selectedTrs = $('table#CurrentUsersTable tr.selected');
+ var theData = {};
+ for (var i = 0; i < countSelected; i++) {
+ theData = $($table
+ .closest('table')
+ .dataTable()
+ .fnGetData(
+ $('table#CurrentUsersTable tr.selected')[i]));
+ theData.rowIndex = $(selectedTrs[i]).index();
+ usersTableDataForEditing.push(theData);
+ }
+
+ //If currentusres table show toolbar
+ if($table.attr('id')==='CurrentUsersTable'){
+// $('div#toolbar').removeClass('hiddenToolbar').addClass('shownToolbar');
+ $('span#numOfSelectedRusersRequestsDataForEditingows').text(rowsCount);
+ $('span#numOfSelectedRows').text(rowsCount);
+ $('div#toolbar').animate({height:'show'});
+ }else if($table.attr('id')==='usersRequestsTable'){
+// $('div#usersRequestsTableToolbarContainer').removeClass('hiddenToolbar').addClass('shownToolbar');
+ $('span#numOfSelectedRowsUserReqs').text(rowsCount);
+ $('div#usersRequestsTableToolbarContainer').animate({height:'show'});
+
+ usersRequestsDataForEditing = [];
+ var index = 0;
+ $.each($('#usersRequestsTable tr.selected td:nth-child(3)'), function(){
+
+ var theData = $($table
+ .dataTable()
+ .fnGetData(
+ $table.find('tr.selected')[index]));
+ usersRequestsDataForEditing.push(theData);
+
+ index++;
+ });
+
+ }else if($table.attr('id')==='GroupTeamsTable'){
+ $('#usersManagementPortletContainerSiteTeamsEditMode #groupTeamsTableToolbarContainer').removeClass('hiddenToolbar').addClass('shownToolbar');
+ }
+ }else{
+// $(this).find('div').text('all')
+// $(this).find('div').css('padding-left','8px');
+
+ $rows.removeClass('selected');
+ $rows.find('.icon-ok').removeClass('whiteFont');
+
+ //If currentusres table hide toolbar
+ if($table.attr('id')==='CurrentUsersTable'){
+// $('div#toolbar').addClass('hiddenToolbar').removeClass('shownToolbar');
+ $('span#numOfSelLectedRows').text('');
+ $('div#toolbar').animate({height:'hide'});
+ }else if($table.attr('id')==='usersRequestsTable'){
+// $('div#usersRequestsTableToolbarContainer').addClass('hiddenToolbar').removeClass('shownToolbar');
+ $('span#numOfSelectedRowsUserReqs').text('');
+ $('div#usersRequestsTableToolbarContainer').animate({height:'hide'});
+ }else if($table.attr('id')==='GroupTeamsTable'){
+ $('#usersManagementPortletContainerSiteTeamsEditMode #groupTeamsTableToolbarContainer').addClass('hiddenToolbar').removeClass('shownToolbar');
+ }
+ }
+ }
+ });
+
+ $('#userNamesList').textext({
+ plugins : ' tags'
+ });
+ $('#userNamesListInAssignRolesModal').textext({
+ plugins : ' tags'
+ });
+ $('#userNamesListInAssignUsersToGroupsModal').textext({
+ plugins : ' tags'
+ });
+
+ $('span#textAboveTagsInput div.row div.text-core:first').addClass('span9');
+ $('span#textAboveTagsInputInAssignRolesModal div.row div.text-core').addClass('span9');
+ $('span#textAboveTagsInputInAssignUsersToGroupsModal div.row div.text-core').addClass('span9');
+
+ $('#userNamesListInAssignUsersToGroupsModal').parent().find('div.text-tags').unbind().bind(
+ 'DOMNodeInserted',
+ function(event) {
+ var element = event.target;
+ var tagName = $(element).prop("tagName");
+ if (tagName !== 'DIV')
+ return;
+ // $('#userNamesList').parent().find('div.text-tag').addClass('span4');
+ $('#userNamesListInAssignUsersToGroupsModal').parent().find('div.text-button').addClass(
+ 'span12');
+ $('#userNamesListInAssignUsersToGroupsModal').parent().find('a.text-remove').html('
')
+ .removeClass('text-remove').addClass('tag-remove');
+ });
+
+ $('#userNamesListInAssignRolesModal').parent().find('div.text-tags').unbind().bind(
+ 'DOMNodeInserted',
+ function(event) {
+ var element = event.target;
+ var tagName = $(element).prop("tagName");
+ if (tagName !== 'DIV')
+ return;
+ // $('#userNamesList').parent().find('div.text-tag').addClass('span4');
+ $('#userNamesListInAssignRolesModal').parent().find('div.text-button').addClass(
+ 'span12');
+ $('#userNamesListInAssignRolesModal').parent().find('a.text-remove').html('
')
+ .removeClass('text-remove').addClass('tag-remove');
+ });
+ $('#userNamesList').parent().find('div.text-tags').unbind().bind(
+ 'DOMNodeInserted',
+ function(event) {
+ var element = event.target;
+ var tagName = $(element).prop("tagName");
+ if (tagName !== 'DIV')
+ return;
+ // $('#userNamesList').parent().find('div.text-tag').addClass('span4');
+ $('#userNamesList').parent().find('div.text-button').addClass(
+ 'span12');
+ $('#userNamesList').parent().find('a.text-remove').html('
')
+ .removeClass('text-remove').addClass('tag-remove');
+ });
+
+ $('#teamsList').parent().find('div.text-tags').off().bind(
+ 'DOMNodeInserted',
+ function(event) {
+ var element = event.target;
+ var tagName = $(element).prop("tagName");
+ if (tagName !== 'DIV')
+ return;
+ // $('#roleList').parent().find('div.text-tag').addClass('span5');
+ $('#teamsList').parent().find('div.text-button').addClass(
+ 'span12');
+ $('#teamsList').parent().find('a.text-remove').html('
')
+ .removeClass('text-remove').addClass('tag-remove');
+ $('textarea#teamsList').parent().find('a.tag-remove').off().on(
+ 'click', function() {
+ $(this).closest('.text-tag').remove();
+ });
+ var matched = false;
+ var tagsTextt = $('#teamsList').parent().find(
+ 'div.text-tag.span5');
+ for (var i = 0; i < tagsTextt.length; i++) {
+ for (var j = i + 1; j < tagsTextt.length; j++) {
+ if ($(tagsTextt[i]).text() === $(tagsTextt[j]).text()) {
+ tagsTextt[j].remove();
+ }
+ }
+ }
+ });
+
+ $('#teamsListInAssignUsersToGroupsModal').parent().find('div.text-tags').off().bind(
+ 'DOMNodeInserted',
+ function(event) {
+ var element = event.target;
+ var tagName = $(element).prop("tagName");
+ if (tagName !== 'DIV')
+ return;
+ // $('#roleList').parent().find('div.text-tag').addClass('span5');
+ $('#teamsListInAssignUsersToGroupsModal').parent().find('div.text-button').addClass(
+ 'span12');
+ $('#teamsListInAssignUsersToGroupsModal').parent().find('a.text-remove').html('
')
+ .removeClass('text-remove').addClass('tag-remove');
+ $('textarea#teamsListInAssignUsersToGroupsModal').parent().find('a.tag-remove').off().on(
+ 'click', function() {
+ $(this).closest('.text-tag').remove();
+ });
+ var matched = false;
+ var tagsTextt = $('#teamsListInAssignUsersToGroupsModal').parent().find(
+ 'div.text-tag.span5');
+ for (var i = 0; i < tagsTextt.length; i++) {
+ for (var j = i + 1; j < tagsTextt.length; j++) {
+ if ($(tagsTextt[i]).text() === $(tagsTextt[j]).text()) {
+ tagsTextt[j].remove();
+ }
+ }
+ }
+ });
+
+ $('#roleList').parent().find('div.text-tags').off().bind(
+ 'DOMNodeInserted',
+ function(event) {
+ var element = event.target;
+ var tagName = $(element).prop("tagName");
+ if (tagName !== 'DIV')
+ return;
+ // $('#roleList').parent().find('div.text-tag').addClass('span5');
+ $('#roleList').parent().find('div.text-button').addClass(
+ 'span12');
+ $('#roleList').parent().find('a.text-remove').html('
')
+ .removeClass('text-remove').addClass('tag-remove');
+ $('textarea#roleList').parent().find('a.tag-remove').off().on(
+ 'click', function() {
+ $(this).closest('.text-tag').remove();
+ });
+ var matched = false;
+ var tagsTextt = $('#roleList').parent().find(
+ 'div.text-tag.span5');
+ for (var i = 0; i < tagsTextt.length; i++) {
+ for (var j = i + 1; j < tagsTextt.length; j++) {
+ if ($(tagsTextt[i]).text() === $(tagsTextt[j]).text()) {
+ tagsTextt[j].remove();
+ }
+ }
+ }
+ });
+
+ $('#roleListInAssignRolesModal').parent().find('div.text-tags').off().bind(
+ 'DOMNodeInserted',
+ function(event) {
+ var element = event.target;
+ var tagName = $(element).prop("tagName");
+ if (tagName !== 'DIV')
+ return;
+ // $('#roleList').parent().find('div.text-tag').addClass('span5');
+ $('#roleListInAssignRolesModal').parent().find('div.text-button').addClass(
+ 'span12');
+ $('#roleListInAssignRolesModal').parent().find('a.text-remove').html('
')
+ .removeClass('text-remove').addClass('tag-remove');
+ $('textarea#roleListInAssignRolesModal').parent().find('a.tag-remove').off().on(
+ 'click', function() {
+ $(this).closest('.text-tag').remove();
+ });
+ var matched = false;
+ var tagsTextt = $('#roleListInAssignRolesModal').parent().find(
+ 'div.text-tag.span5');
+ for (var i = 0; i < tagsTextt.length; i++) {
+ for (var j = i + 1; j < tagsTextt.length; j++) {
+ if ($(tagsTextt[i]).text() === $(tagsTextt[j]).text()) {
+ tagsTextt[j].remove();
+ }
+ }
+ }
+ });
+
+ displaySiteRolesOnHover();
+
+ $('table#CurrentUsersTable tbody').on(
+ 'click',
+ 'tr:not(tr.control) td:first-of-type',
+ function() {
+ $(this).find('i.icon-ok').toggleClass('whiteFont');
+ usersTableDataForEditing = [];
+ userTableUUIDsForEditing = [];
+ $(this).closest('tr').toggleClass('selected');
+ var countSelected = $('table#CurrentUsersTable tr.selected').length;
+ var selectedTrs = $('table#CurrentUsersTable tr.selected');
+ var theData = {};
+ for (var i = 0; i < countSelected; i++) {
+ theData = $($(this)
+ .closest('table')
+ .dataTable()
+ .fnGetData(
+ $('table#CurrentUsersTable tr.selected')[i]));
+ theData.rowIndex = $(selectedTrs[i]).index();
+ usersTableDataForEditing.push(theData);
+ }
+
+ var countSelectedRows = $('table#CurrentUsersTable tr.selected').length;
+ var countTableCells = $('table#CurrentUsersTable tbody tr td').length;
+ if (countTableCells > 1 && countSelectedRows > 0/* && !$('div#toolbar').hasClass('openToolbar')*/) {
+// $('div#toolbar').removeClass('hiddenToolbar')
+// .addClass('shownToolbar');//initially it was just hiding the toolbar, not displaying none
+ $('div#toolbar').animate({height:'show'});
+ $('div#toolbar').addClass('openToolbar');
+ $('span#numOfSelectedRows').text(countSelectedRows);
+ } else if(countSelectedRows === 0){
+// $('div#toolbar').addClass('hiddenToolbar')
+// .removeClass('shownToolbar');//initially it was just hiding the toolbar, not displaying none
+ $('div#toolbar').removeClass('openToolbar');
+ $('span#numOfSelLectedRows').text('');
+ $('div#toolbar').animate({height:'hide'});
+ }
+ //now it toggles it toggles it up and down
+
+ var countTableRows = $('table#CurrentUsersTable tbody tr').length;
+ if(countTableRows === countSelectedRows){
+ $('#CurrentUsersTable th:first').addClass('none');
+ }else {
+ $('#CurrentUsersTable th:first').removeClass('none');
+ }
+ }
+ );
+
+ $('table#usersRequestsTable tbody')
+ .off()
+ .on(
+ 'click',
+ 'tr td:first-of-type',
+ function() {
+ $(this).find('i.icon-ok').toggleClass('whiteFont');
+ usersRequestsDataForEditing = [];
+ usersRequestsMembershipRequestsIdsForEditing = [];
+ $(this).closest('tr').toggleClass('selected');
+ $('#openEditModal').addClass('hidden');
+
+ var countSelected = $('table#usersRequestsTable tr.selected').length;
+ var theData = {};
+ for (var i = 0; i < countSelected; i++) {
+ theData = $($(this)
+ .closest('table')
+ .dataTable()
+ .fnGetData(
+ $('table#usersRequestsTable tr.selected')[i]));
+ usersRequestsDataForEditing.push(theData);
+ }
+
+ var countSelectedRows = $('table#usersRequestsTable tr.selected').length;
+ var countTableCells = $('table#usersRequestsTable tbody tr td').length;
+ if (countTableCells > 1 && countSelectedRows > 0/* && !$('div#usersRequestsTableToolbarContainer').hasClass('openToolbar')*/) {
+// $('div#usersRequestsTableToolbarContainer')
+// .removeClass('hiddenToolbar').addClass(
+// 'shownToolbar');
+ $('div#usersRequestsTableToolbarContainer').animate({height:'show'});
+ $('div#usersRequestsTableToolbarContainer').addClass('openToolbar');
+ $('span#numOfSelectedRowsUserReqs').text(
+ countSelectedRows);
+ } else if(countSelectedRows === 0){
+// $('div#usersRequestsTableToolbarContainer')
+// .addClass('hiddenToolbar').removeClass(
+// 'shownToolbar');
+ $('div#usersRequestsTableToolbarContainer').animate({height:'hide'});
+ $('div#usersRequestsTableToolbarContainer').removeClass('openToolbar');
+ $('span#numOfSelectedRowsUserReqs').text('');
+ }
+
+ var countTableRows = $('table#usersRequestsTable tbody tr').length;
+ if(countTableRows === countSelectedRows){
+ $('#usersRequestsTable th:first').addClass('none');
+ }else {
+ $('#usersRequestsTable th:first').removeClass('none');
+ }
+ });
+
+ $('div#deselectAll').off().on('click', function() {
+ $('#usersManagementPortletContainer .selected').removeClass('selected');
+ $('#usersManagementPortletContainer .whiteFont').removeClass('whiteFont');
+ $('#CurrentUsersTable th:first').removeClass('none');
+// $('div#toolbar').addClass('hiddenToolbar').removeClass('shownToolbar');
+ $('div#toolbar').removeClass('openToolbar');
+ $('div#toolbar').animate({height:'hide'});
+ });
+
+ $('#usersManagementPortletContainer div.toolbarContainer').off().on(
+ 'click',
+ 'div#toolbar.shownToolbar div#editSelected',
+ function() {
+ $('#changeUsersRolesModal').modal('show');
+ $('#roleList').parent().find('.text-tag').remove();
+ $('#teamsList').parent().find('.text-tag').remove();
+ // if(!$('span#textAboveTagsInput div.row
+ // div.text-core:first').hasClass('span9')){
+ //
+ // }
+ currentUsersTableRows = [];
+ var usersEmails = [];
+ for (var i = 0; i < usersTableDataForEditing.length; i++) {
+ var email = usersTableDataForEditing[i][0].Email;
+ currentUsersTableRows
+ .push(usersTableDataForEditing[i].rowIndex);
+ usersEmails.push(email.substring(5, email.length - 6));
+ var userUuid = usersTableDataForEditing[i][0].UserId;
+ userTableUUIDsForEditing.push(userUuid);
+ }
+ var tags = $('textarea#userNamesList').parent().find(
+ 'div.text-tags div.text-tag');
+ if (tags.length > 0)
+ tags.remove();// Remove previous tags
+ $('textarea#userNamesList').textext()[0].tags().addTags(
+ usersEmails);
+ for (var j = 0; j < userTableUUIDsForEditing.length; j++) {
+ var value = userTableUUIDsForEditing[j].toString();
+ $($('#userNamesList').parent().find('.text-tag')[j]).data(
+ 'userUUID', value.substring(5, value.length - 6));
+ }
+ showCheckBoxForSingleTag();
+ $('textarea#userNamesList').parent().find('a.tag-remove').off()
+ .on('click', function() {
+ $(this).closest('.text-tag').remove();
+ showCheckBoxForSingleTag();
+ });
+ }).on('click', 'div#toolbar.shownToolbar div#editTeams',
+ function() {
+
+ $('#changeUsersTeamsModal').modal('show');
+
+ }).on('click', 'div#toolbar.shownToolbar div#deleteSelected',
+ function() {
+
+ $('#deleteUsersFromCurrentSiteModal').modal('show');
+
+
+ }).on('click', 'div#toolbar.shownToolbar div#assignRolesToUser',
+ function() {
+
+ $('#assignUsersRolesModal').modal('show');
+ //remove previous tags
+ $('#roleListInAssignRolesModal').parent().find('.text-tag').remove();
+ currentUsersTableRows = [];
+ var usersEmails = [];
+ for (var i = 0; i < usersTableDataForEditing.length; i++) {
+ var email = usersTableDataForEditing[i][0].Email;
+ currentUsersTableRows
+ .push(usersTableDataForEditing[i].rowIndex);
+ usersEmails.push(email.substring(5, email.length - 6));
+ var userUuid = usersTableDataForEditing[i][0].UserId;
+ userTableUUIDsForEditing.push(userUuid);
+ }
+ var tags = $('textarea#userNamesListInAssignRolesModal').parent().find(
+ 'div.text-tags div.text-tag');
+ if (tags.length > 0)
+ tags.remove();// Remove previous tags
+ $('textarea#userNamesListInAssignRolesModal').textext()[0].tags().addTags(
+ usersEmails);
+
+ for (var j = 0; j < userTableUUIDsForEditing.length; j++) {
+ var value = userTableUUIDsForEditing[j].toString();
+ $($('#userNamesListInAssignRolesModal').parent().find('.text-tag')[j]).data(
+ 'userUUID', value.substring(5, value.length - 6));
+ }
+ showCheckBoxForSingleTagInAssignRolesModal();
+ $('textarea#userNamesListInAssignRolesModal').parent().find('a.tag-remove').off()
+ .on('click', function() {
+ $(this).closest('.text-tag').remove();
+ showCheckBoxForSingleTagInAssignRolesModal();
+ });
+
+ }).on('click', 'div#toolbar.shownToolbar div#assignUsersToGroup',
+ function() {
+
+ $('#assignUsersToGroupsModal').modal('show');
+ //remove previous tags
+ $('#teamsListInAssignUsersToGroupsModal').parent().find('.text-tag').remove();
+ currentUsersTableRows = [];
+ var usersEmails = [];
+ for (var i = 0; i < usersTableDataForEditing.length; i++) {
+ var email = usersTableDataForEditing[i][0].Email;
+ currentUsersTableRows
+ .push(usersTableDataForEditing[i].rowIndex);
+ usersEmails.push(email.substring(5, email.length - 6));
+ var userUuid = usersTableDataForEditing[i][0].UserId;
+ userTableUUIDsForEditing.push(userUuid);
+ }
+ var tags = $('textarea#userNamesListInAssignUsersToGroupsModal').parent().find(
+ 'div.text-tags div.text-tag');
+ if (tags.length > 0 && !filterUserTableByUsersThatDontBelongInAGroup){
+ tags.remove();// Remove previous tags
+ }else if(filterUserTableByUsersThatDontBelongInAGroup){
+ tags.remove();
+// If the user presses add to group from the toolbar the groupname by which
+// he searched must be available in the modal
+ var tags = [];
+ tags.push($('#teamNameHeader').text());
+ $('#teamsListInAssignUsersToGroupsModal').textext()[0].tags().addTags(tags);
+ }
+
+ $('textarea#userNamesListInAssignUsersToGroupsModal').textext()[0].tags().addTags(
+ usersEmails);
+
+ for (var j = 0; j < userTableUUIDsForEditing.length; j++) {
+ var value = userTableUUIDsForEditing[j].toString();
+ $($('#userNamesListInAssignUsersToGroupsModal').parent().find('.text-tag')[j]).data(
+ 'userUUID', value.substring(5, value.length - 6));
+ }
+ showCheckBoxForSingleTagInAssignUsersToGroupsModal();
+ $('textarea#userNamesListInAssignUsersToGroupsModal').parent().find('a.tag-remove').off()
+ .on('click', function() {
+ $(this).closest('.text-tag').remove();
+ showCheckBoxForSingleTagInAssignUsersToGroupsModal();
+ });
+
+ });
+
+ $('button#acceptDeleteUsersFromCurrentSiteModal')
+ .off()
+ .on(
+ 'click',
+ function() {
+
+ var groupId = theGroupId;
+ var doRefresh = true;
+ var selectedRows = $('table#CurrentUsersTable tbody tr.selected');
+ var deletePreviousRoles = false;
+ var deleteUsers = true;
+ var userIDs = [];
+ var roles = [];
+ var reqIDs = [];
+ for (var i = 0; i < selectedRows.length; i++) {
+ var isSelf = $('table#CurrentUsersTable').dataTable().fnGetData(selectedRows[i]).isSelf;
+ var isSelfText = $($.parseHTML(isSelf)).text();
+ if(isSelfText === "true") {
+ $('#deleteUsersFromCurrentSiteModal').modal('hide');
+ $('#cannotRemoveSelfModal').modal('show');
+ return;
+ }
+ var value = $('table#CurrentUsersTable').dataTable().fnGetData(selectedRows[i]).UserId;
+ if (isNaN(value))
+ value = value.substring(5, value.length - 6);
+ userIDs.push(value);
+ var reqID = $('table#CurrentUsersTable')
+ .dataTable().fnGetData(selectedRows[i]).reqID;
+ reqIDs.push(reqID.substring(5, reqID.length - 6));
+ }
+ var sendDismissalEmail = true;//$('#sendAutomaticRejectionEmail').prop('checked');
+ fetchAllCurrentUsers(deleteMode, deleteUsers, userIDs,
+ roles, [], deletePreviousRoles, reqIDs,
+ sendDismissalEmail);
+// $('div#toolbar').addClass('hiddenToolbar').removeClass(
+// 'shownToolbar');
+ $('#deleteUsersFromCurrentSiteModal').modal('hide');
+// $('#sendAutomaticRejectionEmail').prop('checked', false);
+ $('div#toolbar').removeClass('openToolbar');
+ $('div#toolbar').animate({height:'hide'});
+ $('#CurrentUsersTable th:first').removeClass('none');
+ });
+
+ function showCheckBoxForSingleTag() {
+ var currentTags = $('textarea#userNamesList').parent().find(
+ 'div.text-tags div.text-tag');
+ // appendCheckboxHere.append($('
')).append(labelForSingleTag).append(checkBoxForSingleTag);
+ if (currentTags.length == 1) {
+ $('div#singleTagSection').removeClass('hiddenSection');
+ $('div#multipleTagsSection').addClass('hiddenSection');
+ displayRolesForSingleUser();
+ } else {
+ $('div#singleTagSection').addClass('hiddenSection');
+ $('div#multipleTagsSection').removeClass('hiddenSection');
+ }
+ if (!$('#singleTagSection').hasClass('hiddenSection')) {
+ deletePreviousRoles = $('#singleTag').prop('checked');
+ $('#singleTag').prop('checked', false);
+ } else {
+ deletePreviousRoles = false;
+ }
+ }
+
+ function displayRolesForSingleUser() {
+
+ var singleRow = $('table#CurrentUsersTable tbody tr.selected')[0];
+ var table = $('table#CurrentUsersTable');
+ var roles = table.dataTable().fnGetData(singleRow).Roles;
+ var teams = table.dataTable().fnGetData(singleRow).Teams;
+ var rolesText = $(roles).text();//roles is an HTML element, not a jquery one
+ var teamsText = $(teams).text();
+ if(rolesText !== "-"){//"-" means no role
+ var rolesArray = rolesText.split(",");
+ $('textarea#roleList').textext()[0].tags().addTags(rolesArray);
+ }
+ if(teamsText !== "-"){//"-" means no team
+ var teamsArray = teamsText.split(",");
+ $('textarea#teamsList').textext()[0].tags().addTags(teamsArray);
+ }
+ }
+
+ function showCheckBoxForSingleTagInAssignRolesModal() {
+ var currentTags = $('textarea#userNamesListInAssignRolesModal').parent().find(
+ 'div.text-tags div.text-tag');
+ // appendCheckboxHere.append($('
')).append(labelForSingleTag).append(checkBoxForSingleTag);
+ if (currentTags.length == 1) {
+ $('div#singleTagSectionInAssignRolesModal').removeClass('hiddenSection');
+ $('div#multipleTagsSectionInAssignRolesModal').addClass('hiddenSection');
+ displayRolesForSingleUserInAssignRolesModal();
+ } else {
+ $('div#singleTagSectionInAssignRolesModal').addClass('hiddenSection');
+ $('div#multipleTagsSectionInAssignRolesModal').removeClass('hiddenSection');
+ }
+ if (!$('#singleTagSectionInAssignRolesModal').hasClass('hiddenSection')) {
+ deletePreviousRoles = $('#singleTagInAssignRolesModal').prop('checked');
+ $('#singleTagInAssignRolesModal').prop('checked', false);
+ } else {
+ deletePreviousRoles = false;
+ }
+ }
+
+ function displayRolesForSingleUserInAssignRolesModal() {
+
+ var singleRow = $('table#CurrentUsersTable tbody tr.selected')[0];
+ var table = $('table#CurrentUsersTable');
+ var roles = table.dataTable().fnGetData(singleRow).Roles;
+ var teams = table.dataTable().fnGetData(singleRow).Teams;
+ var teamsText = $(teams).text();
+ var rolesText = $(roles).text();//roles is an HTML element, not a jquery one
+ if(rolesText !== "-"){//"-" means no role
+ var rolesArray = rolesText.split(",");
+ $('textarea#roleListInAssignRolesModal').textext()[0].tags().addTags(rolesArray);
+ }
+ }
+
+ function showCheckBoxForSingleTagInAssignUsersToGroupsModal() {
+ var currentTags = $('textarea#userNamesListInAssignUsersToGroupsModal').parent().find(
+ 'div.text-tags div.text-tag');
+ // appendCheckboxHere.append($('
')).append(labelForSingleTag).append(checkBoxForSingleTag);
+ if (currentTags.length == 1) {
+ $('div#singleTagSectionInAssignUsersToGroupsModal').removeClass('hiddenSection');
+ $('div#multipleTagsSectionInAssignUsersToGroupsModal').addClass('hiddenSection');
+ displayRolesForSingleUserInAssignUsersToGroupsModal();
+ } else {
+ $('div#singleTagSectionInAssignUsersToGroupsModal').addClass('hiddenSection');
+ $('div#multipleTagsSectionInAssignUsersToGroupsModal').removeClass('hiddenSection');
+ }
+ if (!$('#singleTagSectionInAssignUsersToGroupsModal').hasClass('hiddenSection')) {
+ deletePreviousRoles = $('#singleTagInAssignUsersToGroupsModal').prop('checked');
+ $('#singleTagInAssignUsersToGroupsModal').prop('checked', false);
+ } else {
+ deletePreviousRoles = false;
+ }
+ }
+
+ function displayRolesForSingleUserInAssignUsersToGroupsModal() {
+
+ var singleRow = $('table#CurrentUsersTable tbody tr.selected')[0];
+ var table = $('table#CurrentUsersTable');
+ var roles = table.dataTable().fnGetData(singleRow).Roles;
+ var teams = table.dataTable().fnGetData(singleRow).Teams;
+ var rolesText = $(roles).text();//roles is an HTML element, not a jquery one
+ var teamsText = $(teams).text();
+ if(teamsText !== "-"){//"-" means no team
+ var teamsArray = teamsText.split(",");
+ $('textarea#teamsListInAssignUsersToGroupsModal').textext()[0].tags().addTags(teamsArray);
+ }
+ }
+
+ $('div#userRequestsNotifications.notificationsShown')
+ .off('click').on(
+ 'click', function() {
+ fetchAllUsersRequests(refreshMode, []);
+ $('div#usersRequestsModal').modal('show');
+ usersRequestsModalIsOpen = true;
+ });
+
+ $('#usersRequestsModal').on('shown', function () {
+ $('.denyClass').addClass('hidden');
+ $('.grantDenyClass').removeClass('hidden');
+
+ $('table#usersRequestsTable').DataTable().columns.adjust().draw();
+ $('table#usersRequestsTable').DataTable().columns.adjust().responsive.recalc();
+ removeArrowFromFirstTableColumn();
+ });
+
+ $('div#userRequestsNotificationsTabletView.notificationsShown')
+ .off('click').on(
+ 'click', function() {
+ fetchAllUsersRequests(refreshMode, []);
+ $('div#usersRequestsModal').modal('show');
+ usersRequestsModalIsOpen = true;
+ });
+
+ $('#usersRequestsModal').on('shown', function () {
+ $('table#usersRequestsTable').DataTable().columns.adjust().draw();
+ $('table#usersRequestsTable').DataTable().columns.adjust().responsive.recalc();
+ removeArrowFromFirstTableColumn();
+ });
+
+ $('a#reloadUsersRequestsTable').off().on(
+ 'click',
+ function() {
+ var reqIds = [];
+ reqIds.push(theGroupId);
+ var organizationId = $('#organizationId').text();
+ // ajaxCallUsersRequests(reqIds, refreshMode, organizationId);
+ $('table#usersRequestsTable').DataTable().clear();
+ fetchAllUsersRequests(refreshMode, []);
+// $('div#usersRequestsTableToolbarContainer').addClass(
+// 'hiddenToolbar').removeClass('shownToolbar');
+ $('div#usersRequestsTableToolbarContainer').animate({height: 'hide'});
+ $('#usersRequestsTable th.none').removeClass('none');
+ // startPreloader();
+ });
+
+ $('#acceptUsersRequestsModal, #rejectUsersRequestsModal').on('hidden', function(){
+ $('#usersRequestsModal').modal('show');
+ });
+
+ $('div#usersRequestsTableContainer').on('click',
+ 'div.usersRequestsTableToolbarContainer.shownToolbar div#acceptSeleced',
+ function() {
+
+ var usersEmails = [];
+ usersRequestsMembershipRequestsIdsForEditing = [];
+ // tagsForEmails
+ for (var i = 0; i < usersRequestsDataForEditing.length; i++) {
+ var email = usersRequestsDataForEditing[i][0].Email;
+ usersEmails.push(email.substring(5,
+ email.length - 6));
+ var reqId = usersRequestsDataForEditing[i][0].RequestId;
+ reqId = reqId.toString().substring(5,
+ reqId.length - 6);
+ usersRequestsMembershipRequestsIdsForEditing
+ .push(reqId);
+ }
+
+ $('#acceptUsersRequestsOk').data('reqIDs',
+ usersRequestsMembershipRequestsIdsForEditing);
+
+ $('#acceptUsersRequestsModal').modal('show');
+
+ $('div.modal-backdrop.fade.in').addClass(
+ 'hideFirstModal');
+
+ $('#usersRequestsModal').modal('hide');
+ });
+
+ $('#acceptUsersRequestsModal').on('hidden', function() {
+ $('div.modal-backdrop.fade.in').removeClass('hideFirstModal');
+ });
+
+ $('#usersRequestsModal').on('hidden', function() {
+ sendCustomMailForMembershipRequestRejection = false;
+ customMailForMembershipRequestRejectionBody = "";
+ $('#emailForRejection').html(automaticRejectionEmailTemplate.html());
+ });
+
+ $('#userDetailsModal').on('hidden',function(){
+ if(usersRequestsDetailModaWasOpen){
+ $('#usersRequestsModal').modal('show');
+ }
+ });
+
+ $('#acceptUsersRequestsOk').off().on('click', function() {
+ var mode = acceptMode;
+ var managerId = $('#userID').text();
+ var reqIds = [];
+ reqIds = $(this).data('reqIDs');
+ // $('textarea#tagsForEmails').parent().find('.text-tag').each(function(){
+ // reqIds.push($(this).data('reqId'));
+ // });
+ var organizationId = $('#organizationId').text();
+ $('div#usersRequestsModal').modal('hide');
+ $('textarea#tagsForEmails').parent().find('.text-tag').remove();
+ if (reqIds.length === 0)
+ return;
+
+ $('table#usersRequestsTable').DataTable().clear();
+ fetchAllUsersRequests(mode, reqIds, managerId, false);
+
+ $('div#usersRequestsTableToolbarContainer').animate({height:'hide'});
+ $('table#usersRequestsTable thead th:first-of-type').removeClass('none');
+
+ $('#acceptUsersRequestsModal').modal('hide');
+
+ });
+
+ $('#rejectUsersRequestsOk').off().on('click', function() {
+ var mode = deleteMode;
+ var managerId = $('#userID').text();
+ var reqIds = [];
+ reqIds = $(this).data('reqIDs');
+ // $('textarea#tagsForEmails').parent().find('.text-tag').each(function(){
+ // reqIds.push($(this).data('reqId'));
+ // });
+ var organizationId = $('#organizationId').text();
+ $('div#usersRequestsModal').modal('hide');
+ $('textarea#tagsForEmails').parent().find('.text-tag').remove();
+ if (reqIds.length === 0){
+ $('#rejectUsersRequestsModal').modal('hide');
+ return;
+ }
+
+
+ $('table#usersRequestsTable').DataTable().clear();
+ fetchAllUsersRequests(mode, reqIds, managerId,
+ false,"");
+
+ $('div#usersRequestsTableToolbarContainer').animate({height:'hide'});
+ $('table#usersRequestsTable thead th:first-of-type').removeClass('none');
+
+ $('#rejectUsersRequestsModal').modal('hide');
+
+ });
+
+ $('div#usersRequestsTableContainer')
+ .on(
+ 'click',
+ 'div.usersRequestsTableToolbarContainer.shownToolbar div#rejectSeleced',
+ function() {
+// $('.grantDenyClass').addClass('hidden');
+// $('.denyClass').removeClass('hidden');
+// $('#emailForRejection').html(automaticRejectionEmailTemplate.html());
+// $('#emailForRejection').html(
+// $('#emailForRejection').html()
+// .replace('%site%', $('#groupName').text())
+// .replace('%portalName%', portalName)
+// .replace('%adminName%', $('#adminName').text())
+// );
+// $('#editEmailTemplate').tooltip();
+//
+// // allButtons hide
+// $('#closeUsersRolesModal').data('btnData', 1);
+// if (!$('div#emailForAcceptance').parent().hasClass(
+// 'hideSection'))
+// $('div#emailForAcceptance').parent().addClass(
+// 'hideSection');
+// $('div#emailForRejection').parent().removeClass(
+// 'hideSection');
+// var width = $('div#usersRequestsModal div.modal-body')
+// .width();
+// width = '' + width + 'px';
+// $('div#usersRequestsTableContainer').animate(
+// {
+// right : width
+// },
+// '2500',
+// function() {
+// $('div#usersRequestsTableContainer')
+// .toggleClass('hideSection');
+// $('div#requestsAcceptanceContainer')
+// .toggleClass('hideSection').css({
+// left : '0px'
+// });
+// });
+// $('#userRequestsHeader').contents().first()[0].textContent = 'Requests rejection';
+// $('button#sendRejection').toggleClass('hideButton');
+// $('button#acceptAll').toggleClass('hideButton');
+// $('button#rejectAll').toggleClass('hideButton');
+//
+// var usersEmails = [];
+// usersRequestsMembershipRequestsIdsForEditing = [];
+// // tagsForEmails
+// for (var i = 0; i < usersRequestsDataForEditing.length; i++) {
+// var email = usersRequestsDataForEditing[i][0].Email;
+// usersEmails.push(email.substring(5,
+// email.length - 6));
+// var reqId = usersRequestsDataForEditing[i][0].RequestId;
+// usersRequestsMembershipRequestsIdsForEditing
+// .push(reqId);
+// }
+//
+// var tags = $('textarea#tagsForEmails').parent().find(
+// 'div.text-tags div.text-tag');
+// if (tags.length > 0)
+// tags.remove();
+//
+// $('textarea#tagsForEmails').textext()[0].tags().addTags(usersEmails);
+// for (var j = 0; j < usersRequestsMembershipRequestsIdsForEditing.length; j++) {
+// var theReqId = usersRequestsMembershipRequestsIdsForEditing[j]
+// .toString();
+// $($('#tagsForEmails').parent().find('.text-tag')[j])
+// .data(
+// 'reqId',
+// theReqId.substring(5,
+// theReqId.length - 6));
+// }
+//
+// $('textarea#tagsForEmails').parent().find(
+// 'a.tag-remove').off().on('click', function() {
+// $(this).closest('.text-tag').remove();
+// });
+//
+// $('#reloadUsersRequestsTable').addClass('hide');
+
+
+
+
+
+
+
+// var mode = deleteMode;
+ var reqIds = [];
+ for (var i = 0; i < usersRequestsDataForEditing.length; i++) {
+ var reqId = usersRequestsDataForEditing[i][0].RequestId;
+ reqIds.push($(reqId).text());
+ }
+
+ // startPreloader();
+// fetchAllUsersRequests(mode, reqIds, managerId,
+// false,"");
+
+ $('#rejectUsersRequestsOk').data('reqIDs',
+ reqIds);
+
+ $('#usersRequestsModal').modal('hide');
+ $('#rejectUsersRequestsModal').modal('show');
+
+ });
+
+ $('div#clickToGoBack').off().on(
+ 'click',
+ function() {
+ $('.denyClass').addClass('hidden');
+ $('.grantDenyClass').removeClass('hidden');
+ screenToTheLeft();
+ $('#reloadUsersRequestsTable').removeClass('hide');
+
+ $('#userEditedMailTemplate').closest('div').replaceWith('');
+ if ($('#emailForRejection').length !== 1) {
+ $('div#requestsAcceptanceBody .row:last').append(
+ automaticRejectionEmailTemplate);
+ $('#editEmailTemplate').tooltip();
+ }
+ });
+
+ $('button#saveUsersRolesModal').off().on(
+ 'click',
+ function() {
+ var groupId = theGroupId;
+ var trueFalse = true;
+ var mode = acceptMode;
+ var deleteUsers = false;
+ var ajaxData = [];
+ for (var i = 0; i < $($('#userNamesList').parent().find(
+ '.text-tag')).length; i++) {
+ ajaxData.push($(
+ $('#userNamesList').parent().find('.text-tag')[i])
+ .data('userUUID'));
+ }
+ var roles = [];
+ $('#roleList').parent().find('div.text-tag span.text-label')
+ .each(function() {
+ roles.push($(this).text());
+ });
+
+ var teams = [];
+ $('#teamsList').parent().find('div.text-tag span.text-label')
+ .each(function() {
+ teams.push($(this).text().trim());
+ });
+
+// if (!deletePreviousRoles && (roles.length === 0 || teams.length)){
+//
+// return;
+// }
+ var deletePreviousRoles2 = true;//If you remove a role, the roles should be updated even though the checkbox might be unchecked
+
+ fetchAllCurrentUsers(mode, deleteUsers, ajaxData, roles, teams, deletePreviousRoles2, [], false, MASS_EDIT_USERS);
+ $('table#CurrentUsersTable tr.selected').removeClass('selected');
+ $('#changeUsersRolesModal').modal('hide');
+// $('div#toolbar').addClass('hiddenToolbar').removeClass('shownToolbar');
+ $('div#toolbar').removeClass('openToolbar');
+ $('div#toolbar').animate({height:'hide'});
+ $('#CurrentUsersTable th:first').removeClass('none');
+ $('span#numOfSelLectedRows').text('');
+ }
+ );
+
+ $('button#saveUsersRolesModalInAssignRolesModal').off().on(
+ 'click',
+ function() {
+ var groupId = theGroupId;
+ var trueFalse = true;
+ var mode = acceptMode;
+ var deleteUsers = false;
+ var ajaxData = [];
+ for (var i = 0; i < $($('#userNamesListInAssignRolesModal').parent().find(
+ '.text-tag')).length; i++) {
+ ajaxData.push($(
+ $('#userNamesListInAssignRolesModal').parent().find('.text-tag')[i])
+ .data('userUUID'));
+ }
+ var roles = [];
+ $('#roleListInAssignRolesModal').parent().find('div.text-tag span.text-label')
+ .each(function() {
+ roles.push($(this).text());
+ });
+
+ var teams = [];
+
+ var deletePreviousRoles2 = true;//If you remove a role, the roles should be updated even though the checkbox might be unchecked
+
+ fetchAllCurrentUsers(mode, deleteUsers, ajaxData, roles, teams, deletePreviousRoles2, [], false, ASSIGN_ROLES_TO_USERS);
+ $('table#CurrentUsersTable tr.selected').removeClass('selected');
+ $('#assignUsersRolesModal').modal('hide');
+// $('div#toolbar').addClass('hiddenToolbar').removeClass('shownToolbar');
+ $('span#numOfSelLectedRows').text('');
+ $('#CurrentUsersTable th:first').removeClass('none');
+// $('div#toolbar').addClass('hiddenToolbar').removeClass('shownToolbar');
+ $('div#toolbar').removeClass('openToolbar');
+ $('div#toolbar').animate({height:'hide'});
+ $('#CurrentUsersTable th:first').removeClass('none');
+ }
+ );
+
+ $('button#saveUsersTeamsInAssignUsersToGroupsModal').off().on(
+ 'click',
+ function() {
+ var groupId = theGroupId;
+ var trueFalse = true;
+ var mode = acceptMode;
+ var deleteUsers = false;
+ var ajaxData = [];
+ for (var i = 0; i < $($('#userNamesListInAssignUsersToGroupsModal').parent().find(
+ '.text-tag')).length; i++) {
+ ajaxData.push($(
+ $('#userNamesListInAssignUsersToGroupsModal').parent().find('.text-tag')[i])
+ .data('userUUID'));
+ }
+ var roles = [];
+ var teams = [];
+ $('#teamsListInAssignUsersToGroupsModal').parent().find('div.text-tag span.text-label')
+ .each(function() {
+ teams.push($(this).text().trim());
+ });
+
+ var deletePreviousRoles2 = true;//If you remove a role, the roles should be updated even though the checkbox might be unchecked
+
+ fetchAllCurrentUsers(mode, deleteUsers, ajaxData, roles, teams, deletePreviousRoles2, [], false, ASSIGN_TEAMS_TO_USERS);
+ $('table#CurrentUsersTable tr.selected').removeClass('selected');
+ $('#assignUsersToGroupsModal').modal('hide');
+// $('div#toolbar').addClass('hiddenToolbar').removeClass('shownToolbar');
+ $('span#numOfSelLectedRows').text('');
+ $('div#toolbar').removeClass('openToolbar');
+ $('div#toolbar').animate({height:'hide'});
+ $('#CurrentUsersTable th:first').removeClass('none');
+ }
+ );
+
+ $('button#sendAcceptance').off().on('click', function() {
+ var mode = acceptMode;
+ var managerId = $('#userID').text();
+ var reqIds = [];
+ $('textarea#tagsForEmails').parent().find('.text-tag').each(function() {
+ reqIds.push($(this).data('reqId'));
+ });
+ var organizationId = $('#organizationId').text();
+ $('div#usersRequestsModal').modal('hide');
+ $('textarea#tagsForEmails').parent().find('.text-tag').remove();
+ if (reqIds.length === 0)
+ return;
+ screenToTheLeft();
+ // startPreloader();
+ fetchAllUsersRequests(mode, reqIds, managerId);
+ // ajaxCallUsersRequests(reqIds, acceptMode, organizationId);
+ });
+
+ $('button#sendRejection').off()
+ .on(
+ 'click',
+ function() {
+ var mode = deleteMode;
+ var managerId = $('#userID').text();
+ var reqIds = [];
+ $('textarea#tagsForEmails').parent().find('.text-tag')
+ .each(function() {
+ reqIds.push($(this).data('reqId'));
+ });
+ var organizationId = $('#organizationId').text();
+ $('div#usersRequestsModal').modal('hide');
+ $('textarea#tagsForEmails').parent().find('.text-tag')
+ .remove();
+ if (reqIds.length === 0)
+ return;
+ screenToTheLeft();
+
+ customMailForMembershipRequestRejectionBody = $('#userEditedMailTemplate').val();
+
+ if(customMailForMembershipRequestRejectionBody !== undefined){
+ customMailForMembershipRequestRejectionBody = customMailForMembershipRequestRejectionBody.replace(/\n/g,"
");
+ }
+
+ // startPreloader();
+ fetchAllUsersRequests(mode, reqIds, managerId,
+ sendCustomMailForMembershipRequestRejection,
+ customMailForMembershipRequestRejectionBody);
+ // ajaxCallUsersRequests(reqIds, deleteMode,
+ // organizationId);
+
+// fetchAllRejectedUsersRequests();
+ });
+
+ $('#userRequestsNotifications #notificationsNumberPlaceHolder, #userRequestsNotificationsTabletView #notificationsNumberPlaceHolderTabletView').off('DOMNodeInserted').bind(
+ 'DOMNodeInserted',
+ function(event) {
+ $('#closeUsersRolesModal').data('btnData', 0);
+ if ($(this).text() === '0' || $(this).text() === '') {
+ $(this).parent().removeClass('notificationsShown').addClass(
+ 'notificationsHidden');
+ if($(this).text() === '0'){
+ return;
+ }else{
+ $(this).text('0');
+ }
+// $('#usersManagementDiv').text('No Pending Requests');
+ } else {
+ $(this).parent().removeClass('notificationsHidden').addClass(
+ 'notificationsShown');
+// $('#usersManagementDiv').text('Pending Requests:');
+ }
+ });
+
+ if ($('#userRequestsNotifications #notificationsNumberPlaceHolder').text() === '0') {
+ $('#userRequestsNotifications').removeClass('notificationsShown')
+ .addClass('notificationsHidden');
+ }
+ if ($('#userRequestsNotificationsTabletView #notificationsNumberPlaceHolderTabletView').text() === '0') {
+ $('#userRequestsNotificationsTabletView').removeClass('notificationsShown')
+ .addClass('notificationsHidden');
+ }
+
+ $('button#acceptAll').off().on(
+ 'click',
+ function() {
+ var mode = acceptMode;
+ var managerId = $('#userID').text();
+ var existingTrs = $('table#usersRequestsTable tbody tr');
+ var reqIDs = [];
+ for (var i = 0; i < existingTrs.length; i++) {
+ var data = $($('table#usersRequestsTable').dataTable()
+ .fnGetData(existingTrs[i]));
+ var reqID = data[0].RequestId;
+ reqIDs.push(reqID.substring(5, reqID.length - 6));
+ }
+ var organizationId = $('#organizationId').text();
+ fetchAllUsersRequests(mode, reqIDs, managerId);
+ $('div#usersRequestsModal').modal('hide');
+ });
+
+ $('button#rejectAll').off().on(
+ 'click',
+ function() {
+// var mode = deleteMode;
+// var managerId = $('#userID').text();
+// var existingTrs = $('table#usersRequestsTable tbody tr');
+// var reqIDs = [];
+// for (var i = 0; i < existingTrs.length; i++) {
+// var data = $($('table#usersRequestsTable').dataTable()
+// .fnGetData(existingTrs[i]));
+// var reqID = data[0].RequestId;
+// reqIDs.push(reqID.substring(5, reqID.length - 6));
+// }
+// fetchAllUsersRequests(mode, reqIDs, managerId, false, "");
+// $('div#usersRequestsModal').modal('hide');
+ if($('#usersRequestsTable th:first').hasClass('none')){
+ var trs = $('#usersRequestsTable tbody tr');
+ $.each(trs, function(){
+ $(this).find('td:first-of-type').click();
+ });
+ $.each(trs, function(){
+ $(this).find('td:first-of-type').click();
+ });
+ }else {
+ var trs = $('#usersRequestsTable tbody tr:not(.selected)');
+ $.each(trs, function(){
+ $(this).find('td:first-of-type').click();
+ });
+ }
+ $('#rejectSeleced').click();
+ });
+
+ $('a#currentUsersTableRefresh').off().on(
+ 'click',
+ function() {
+ var groupId = theGroupId;
+ var doRefresh = true;
+ var mode = refreshMode;
+ var selectedUsers = [];
+ var roles = [];
+ // startPreloader();
+ fetchAllCurrentUsers(2, false, [], [], false, [], false);
+ // ajaxCallCurrentUsers(groupId, doRefresh, mode, selectedUsers,
+ // roles, false);
+// if ($('div#toolbar').hasClass('shownToolbar'))
+// $('div#toolbar').addClass('hiddenToolbar').removeClass(
+// 'shownToolbar');
+ });
+
+ $(document).on('click', 'button#editEmailTemplate', function() {
+ var automaticTemplate = $('#emailForRejection');
+ automaticTemplate.find('div.tooltip.fade.top.in').remove();
+ var text = automaticTemplate.text();
+ var div = $('
', {
+ 'class' : 'span11'
+ });
+ var textarea = $('
', {
+ id : 'userEditedMailTemplate',
+ text : text.trim(),
+ 'class' : 'span12',
+ rows : 7
+ });
+ div.append(textarea);
+ automaticTemplate.replaceWith(div);
+
+ // Setting this parameter to true means the admin wants to send a custom
+ // mail to the user whose request is being rejected.
+ sendCustomMailForMembershipRequestRejection = true;
+ });
+
+ $('#openEditModal').off('click').on('click', function(){
+ $('#userDetailsModal').modal('hide');
+ if(keepTrackOfUsersTableRow !== -1){
+ var htmlRow = $('#CurrentUsersTable tbody tr')[keepTrackOfUsersTableRow];
+ var $Row = $(htmlRow);
+ $Row.find('td:first').trigger('click');
+ $('div#toolbar.shownToolbar div#editSelected').trigger('click');
+ }
+ keepTrackOfUsersTableRow = -1;
+ });
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/tabs.js b/src/main/webapp/js/tabs.js
new file mode 100644
index 0000000..1c28c7f
--- /dev/null
+++ b/src/main/webapp/js/tabs.js
@@ -0,0 +1,38 @@
+function tabsFunctionality(){
+ $('table#CurrentUsersTable').DataTable().columns.adjust().draw();
+ $('table#CurrentUsersTable').DataTable().columns.adjust().responsive.recalc();
+
+ $('li.unhit').on('click', function(){
+ if($(this).hasClass('unhit')){
+ $(this).removeClass('unhit');
+ initializeGroupTeamsTable();
+ fetchAllSiteTeamsForTheCurrentGroup();
+ searchInputFixForSiteTeamsEditTable();
+ siteTeamsTableEvents();
+ constructToolbarForSiteTeamsTable();
+ initializeSiteTeamUsersTable();
+ searchInputFixForSiteTeamsUsersTable();
+
+ setTimeout(function(){//If you don't add some time interval, the table won't redraw when you press the tab
+ $('table#GroupTeamsTable').DataTable().columns.adjust().draw();
+ $('table#GroupTeamsTable').DataTable().columns.adjust().responsive.recalc();
+
+ removeArrowFromFirstTableColumn();
+ },200);
+ }
+ });
+// a.lineBeneathTabTitle
+ $(' ul#myTab li ').on('click', function(){
+// $(this).prev().tab('show');//tab('show') applies on data-toggle="tab" element, only
+ var $appropriateTab = $(this).find('a.tabTitle');
+ $appropriateTab.tab('show');
+ if($(this).attr('id')==='rejectedUsersRequestsManagement'){
+ setTimeout(function(){//If you don't add some time interval, the table won't redraw when you press the tab
+ $('table#rejectedUsersRequestsTable').DataTable().columns.adjust().draw();
+ $('table#rejectedUsersRequestsTable').DataTable().columns.adjust().responsive.recalc();
+
+ removeArrowFromFirstTableColumn();
+ },200);
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/tagsInputInSearchInput.js b/src/main/webapp/js/tagsInputInSearchInput.js
new file mode 100644
index 0000000..67ac710
--- /dev/null
+++ b/src/main/webapp/js/tagsInputInSearchInput.js
@@ -0,0 +1,35 @@
+function addTagsInputFunctionalityToSearchInput(){
+ var input = $('
',{
+ id: 'tagsForWhenYouWantToAssignUsersToGroups',
+ 'data-role': 'tagsinput',
+ css : {
+ display: 'none'
+ },
+ type: 'text'
+ });
+ $('#CurrentUsersTable_filter label').append(input);
+
+ $('#tagsForWhenYouWantToAssignUsersToGroups').tagsinput({
+ trimValue: true
+ });
+ $('#tagsForWhenYouWantToAssignUsersToGroups').on('itemAdded',function(){
+ var tagsTexts = $('div.bootstrap-tagsinput span.label-info');
+ $.each(tagsTexts, function(index,value){
+ var val = $(this).html();
+ if(!(val.indexOf('#') > -1)){
+ $(this).html('#' + val);
+ }
+ });
+ }).on('itemRemoved',function(){
+ $('table#CurrentUsersTable').DataTable().columns( 5 ).search('', true, false).draw();
+ $('table#CurrentUsersTable th:first').removeClass('sorting_asc');
+ filterUserTableByUsersThatDontBelongInAGroup = false;
+ var countTableRows = $('table#CurrentUsersTable tbody tr').length;
+ var countSelectedRows = $('table#CurrentUsersTable tr.selected').length;
+ if(countTableRows === countSelectedRows){
+ $('#CurrentUsersTable th:first').addClass('none');
+ }else {
+ $('#CurrentUsersTable th:first').removeClass('none');
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/togglePreloader.js b/src/main/webapp/js/togglePreloader.js
new file mode 100644
index 0000000..161c036
--- /dev/null
+++ b/src/main/webapp/js/togglePreloader.js
@@ -0,0 +1,12 @@
+function showPreloader(){
+ setTimeout(function(){
+ $('div#blanket').addClass('shown');
+ $('div#blanket p#preloader').removeClass('hiddenPreloader');
+ }, 500);
+}
+function hidePreloader(){
+ setTimeout(function(){
+ $('div#blanket').removeClass('shown');
+ $('div#blanket p#preloader').addClass('hiddenPreloader');
+ }, 500);
+}
\ No newline at end of file
diff --git a/src/main/webapp/views/usersManagement.jsp b/src/main/webapp/views/usersManagement.jsp
new file mode 100644
index 0000000..cf0e098
--- /dev/null
+++ b/src/main/webapp/views/usersManagement.jsp
@@ -0,0 +1,801 @@
+<%@page contentType="text/html; charset=utf-8" pageEncoding="UTF-8" %>
+<%@page import="com.liferay.portal.kernel.util.StringPool" %>
+<%@page import="java.util.List"%>
+<%@page import="com.liferay.portal.service.RoleLocalServiceUtil"%>
+<%@page import="com.liferay.portal.model.Role"%>
+<%@page import="com.liferay.portal.model.Team"%>
+<%@page import="com.liferay.portal.service.TeamLocalServiceUtil" %>
+
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
+<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
+<%@ taglib uri="http://liferay.com/tld/theme" prefix="theme" %>
+<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>
+<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>
+
+
+
+
+
+
<%=loginURL.toString() %>
+
<%=user.getUserId()%>
+
<%=user.getFullName()%>
+
<%=themeDisplay.getLayout().getGroup().getName()%>
+
+
+
+
+
+">
+
+
+">
+">
+">
+
+">
+">
+
+
+<%--
+
+
+
+
Go to another JSP --%>
+
+
+
+ "/>
+
+
+
+
+
+
+
+
+
+
+
+
+
An internal server error has occured. Please try again later
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ username
+
+
+ email
+
+
+ full name
+
+
+ roles
+
+
+ groups
+
+
+ request date
+
+
+ validation date
+
+
+ acceptance manager
+
+
+
+
+
+
+
+
+
+
+
+
+
+ users selected
+
+
+
+ roles
+
+
+
+ groups
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ users selected
+
+
+
+ roles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ users selected
+
+
+
+ groups
+
+
+
+
+
+
+
+
+
+
+
+
One or both roles, groups fields are empty.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ username
+
+
+ email
+
+
+ full name
+
+
+ messages
+
+
+ request date
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Το :
+
+
+
+
+
+
+
Message:
+
+
+
+
+ Dear Sir / Madam,
+
+ Your request for accessing the %site% at: %portalName% has been rejected by %adminName%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Are you sure you want to accept the selected users' membership requests?
+
+
+
+
+
+
+
+
Are you sure you want to reject the selected users' membership requests?
+
+
+
+
+
+
+
+
Are you sure you want to remove the selected users?
+
+
+
+
+
+
+
+
+
+
+
+
You are not allowed to remove yourself from the current group.
+
+
+
+
+
+
+
+
+
+
+
+
+
An internal server error has occured. Please try again later
+
+
+
+
+
+
+
+
+
+
+
+ Full Name
+
+
+ UserName
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ name
+
+
+ description
+
+
+ number of users
+
+
+ creation date
+
+
+ last modification date
+
+
+ creator name
+
+
+
+
+
+
+
+
+
+
+
+
+ Description:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Are you sure you want to delete the selected group?
+
+
+
+
+
+
+
+
+
+
+
+ Description:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ username
+
+
+ email
+
+
+ full name
+
+
+ messages
+
+
+ request date
+
+
+ rejection date
+
+
+
+
+
+
+
+
+
+
+
+
+">
+
+">
+">
+">
+">
+">
+">
+">
+">
+">
+
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+">
+
+">
+">
+">
+">
+">
+">
+">
+">
+">
+">
+">
+
+">
+
+
" rel="stylesheet">
+
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
" rel="stylesheet">
+
+
+
\ No newline at end of file