├── .gitignore
├── SpringSecurityApp.iml
├── pom.xml
└── src
└── main
├── java
└── net
│ └── proselyte
│ └── springsecurityapp
│ ├── controller
│ └── UserController.java
│ ├── dao
│ ├── RoleDao.java
│ └── UserDao.java
│ ├── model
│ ├── Role.java
│ └── User.java
│ ├── service
│ ├── SecurityService.java
│ ├── SecurityServiceImpl.java
│ ├── UserDetailsServiceImpl.java
│ ├── UserService.java
│ └── UserServiceImpl.java
│ └── validator
│ └── UserValidator.java
├── resources
├── database.properties
├── database.sql
├── logback.xml
└── validation.properties
└── webapp
├── WEB-INF
├── appconfig-data.xml
├── appconfig-mvc.xml
├── appconfig-root.xml
├── appconfig-security.xml
├── views
│ ├── admin.jsp
│ ├── login.jsp
│ ├── registration.jsp
│ └── welcome.jsp
└── web.xml
└── resources
├── css
├── bootstrap.min.css
└── common.css
└── js
└── bootstrap.min.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/*
2 | *.class
3 | target/*
4 |
5 | /.war
6 | /.ear
7 | /.jar
--------------------------------------------------------------------------------
/SpringSecurityApp.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | net.proselyte
5 | SpringSecurityApp
6 | war
7 | 1.0-SNAPSHOT
8 | SpringSecurityApp Maven Webapp
9 | http://maven.apache.org
10 |
11 |
12 | 4.2.0.RELEASE
13 | 4.0.2.RELEASE
14 | 1.8.2.RELEASE
15 | 4.3.11.Final
16 | 5.2.1.Final
17 | 5.1.36
18 | 1.4
19 | 1.2
20 | 3.8.1
21 | 1.1.3
22 | 1.8
23 | 1.8
24 |
25 |
26 |
27 |
28 | org.springframework
29 | spring-web
30 | ${spring.version}
31 |
32 |
33 |
34 | org.springframework
35 | spring-webmvc
36 | ${spring.version}
37 |
38 |
39 |
40 | org.springframework.security
41 | spring-security-web
42 | ${spring-security.version}
43 |
44 |
45 |
46 | org.springframework.security
47 | spring-security-config
48 | ${spring-security.version}
49 |
50 |
51 |
52 | org.hibernate
53 | hibernate-validator
54 | ${hibernate-validator.version}
55 |
56 |
57 |
58 | org.springframework.data
59 | spring-data-jpa
60 | ${spring-data-jpa.version}
61 |
62 |
63 |
64 | org.hibernate
65 | hibernate-entitymanager
66 | ${hibernate.version}
67 |
68 |
69 |
70 | mysql
71 | mysql-connector-java
72 | ${mysql-connector.version}
73 |
74 |
75 |
76 | commons-dbcp
77 | commons-dbcp
78 | ${commons-dbcp.version}
79 |
80 |
81 |
82 | javax.servlet
83 | jstl
84 | ${jstl.version}
85 |
86 |
87 |
88 | junit
89 | junit
90 | ${junit.version}
91 | test
92 |
93 |
94 |
95 | ch.qos.logback
96 | logback-classic
97 | ${logback.version}
98 |
99 |
100 |
101 | SpringSecurityApp
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/controller/UserController.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.controller;
2 |
3 | import net.proselyte.springsecurityapp.model.User;
4 | import net.proselyte.springsecurityapp.service.SecurityService;
5 | import net.proselyte.springsecurityapp.service.UserService;
6 | import net.proselyte.springsecurityapp.validator.UserValidator;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.stereotype.Controller;
9 | import org.springframework.ui.Model;
10 | import org.springframework.validation.BindingResult;
11 | import org.springframework.web.bind.annotation.ModelAttribute;
12 | import org.springframework.web.bind.annotation.RequestMapping;
13 | import org.springframework.web.bind.annotation.RequestMethod;
14 |
15 | /**
16 | * Controller for {@link net.proselyte.springsecurityapp.model.User}'s pages.
17 | *
18 | * @author Eugene Suleimanov
19 | * @version 1.0
20 | */
21 |
22 | @Controller
23 | public class UserController {
24 |
25 | @Autowired
26 | private UserService userService;
27 |
28 | @Autowired
29 | private SecurityService securityService;
30 |
31 | @Autowired
32 | private UserValidator userValidator;
33 |
34 | @RequestMapping(value = "/registration", method = RequestMethod.GET)
35 | public String registration(Model model) {
36 | model.addAttribute("userForm", new User());
37 |
38 | return "registration";
39 | }
40 |
41 | @RequestMapping(value = "/registration", method = RequestMethod.POST)
42 | public String registration(@ModelAttribute("userForm") User userForm, BindingResult bindingResult, Model model) {
43 | userValidator.validate(userForm, bindingResult);
44 |
45 | if (bindingResult.hasErrors()) {
46 | return "registration";
47 | }
48 |
49 | userService.save(userForm);
50 |
51 | securityService.autoLogin(userForm.getUsername(), userForm.getConfirmPassword());
52 |
53 | return "redirect:/welcome";
54 | }
55 |
56 | @RequestMapping(value = "/login", method = RequestMethod.GET)
57 | public String login(Model model, String error, String logout) {
58 | if (error != null) {
59 | model.addAttribute("error", "Username or password is incorrect.");
60 | }
61 |
62 | if (logout != null) {
63 | model.addAttribute("message", "Logged out successfully.");
64 | }
65 |
66 | return "login";
67 | }
68 |
69 | @RequestMapping(value = {"/", "/welcome"}, method = RequestMethod.GET)
70 | public String welcome(Model model) {
71 | return "welcome";
72 | }
73 |
74 | @RequestMapping(value = "/admin", method = RequestMethod.GET)
75 | public String admin(Model model) {
76 | return "admin";
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/dao/RoleDao.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.dao;
2 |
3 | import net.proselyte.springsecurityapp.model.Role;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | public interface RoleDao extends JpaRepository {
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/dao/UserDao.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.dao;
2 |
3 | import net.proselyte.springsecurityapp.model.User;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | public interface UserDao extends JpaRepository {
7 | User findByUsername(String username);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/model/Role.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.model;
2 |
3 |
4 | import javax.persistence.*;
5 | import java.util.Set;
6 |
7 | /**
8 | * Simple JavaBean object that represents role of {@link User}.
9 | *
10 | * @author Eugene Suleimanov
11 | * @version 1.0
12 | */
13 |
14 | @Entity
15 | @Table(name = "roles")
16 | public class Role {
17 |
18 | @Id
19 | @GeneratedValue(strategy = GenerationType.AUTO)
20 | private Long id;
21 |
22 | @Column(name = "name")
23 | private String name;
24 |
25 | @ManyToMany(mappedBy = "roles")
26 | private Set users;
27 |
28 | public Role() {
29 | }
30 |
31 | public Long getId() {
32 | return id;
33 | }
34 |
35 | public void setId(Long id) {
36 | this.id = id;
37 | }
38 |
39 | public String getName() {
40 | return name;
41 | }
42 |
43 | public void setName(String name) {
44 | this.name = name;
45 | }
46 |
47 | public Set getUsers() {
48 | return users;
49 | }
50 |
51 | public void setUsers(Set users) {
52 | this.users = users;
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return "Role{" +
58 | "id=" + id +
59 | ", name='" + name + '\'' +
60 | ", users=" + users +
61 | '}';
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/model/User.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.model;
2 |
3 | import javax.persistence.*;
4 | import java.util.Set;
5 |
6 | /**
7 | * Simple JavaBean domain object that represents a User.
8 | *
9 | * @author Eugene Suleimanov
10 | * @version 1.0
11 | */
12 |
13 | @Entity
14 | @Table(name = "users")
15 | public class User {
16 |
17 | @Id
18 | @GeneratedValue(strategy = GenerationType.AUTO)
19 | private Long id;
20 |
21 | @Column(name = "username")
22 | private String username;
23 |
24 | @Column(name = "password")
25 | private String password;
26 |
27 | @Transient
28 | private String confirmPassword;
29 |
30 | @ManyToMany
31 | @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"),
32 | inverseJoinColumns = @JoinColumn(name = "role_id"))
33 | private Set roles;
34 |
35 | public Long getId() {
36 | return id;
37 | }
38 |
39 | public void setId(Long id) {
40 | this.id = id;
41 | }
42 |
43 | public String getUsername() {
44 | return username;
45 | }
46 |
47 | public void setUsername(String username) {
48 | this.username = username;
49 | }
50 |
51 | public String getPassword() {
52 | return password;
53 | }
54 |
55 | public void setPassword(String password) {
56 | this.password = password;
57 | }
58 |
59 | public String getConfirmPassword() {
60 | return confirmPassword;
61 | }
62 |
63 | public void setConfirmPassword(String confirmPassword) {
64 | this.confirmPassword = confirmPassword;
65 | }
66 |
67 | public Set getRoles() {
68 | return roles;
69 | }
70 |
71 | public void setRoles(Set roles) {
72 | this.roles = roles;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/service/SecurityService.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.service;
2 |
3 | /**
4 | * Service for Security.
5 | *
6 | * @author Eugene Suleimanov
7 | * @version 1.0
8 | */
9 |
10 | public interface SecurityService {
11 |
12 | String findLoggedInUsername();
13 |
14 | void autoLogin(String username, String password);
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/service/SecurityServiceImpl.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.service;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.security.authentication.AuthenticationManager;
7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
8 | import org.springframework.security.core.context.SecurityContextHolder;
9 | import org.springframework.security.core.userdetails.UserDetails;
10 | import org.springframework.security.core.userdetails.UserDetailsService;
11 | import org.springframework.stereotype.Service;
12 |
13 | /**
14 | * Implementation of {@link SecurityService} interface.
15 | *
16 | * @author Eugene Suleimanov
17 | * @version 1.0
18 | */
19 |
20 | @Service
21 | public class SecurityServiceImpl implements SecurityService {
22 |
23 | private static final Logger logger = LoggerFactory.getLogger(SecurityServiceImpl.class);
24 |
25 | @Autowired
26 | private AuthenticationManager authenticationManager;
27 |
28 | @Autowired
29 | private UserDetailsService userDetailsService;
30 |
31 | @Override
32 | public String findLoggedInUsername() {
33 | Object userDetails = SecurityContextHolder.getContext().getAuthentication().getDetails();
34 | if (userDetails instanceof UserDetails) {
35 | return ((UserDetails) userDetails).getUsername();
36 | }
37 |
38 | return null;
39 | }
40 |
41 | @Override
42 | public void autoLogin(String username, String password) {
43 | UserDetails userDetails = userDetailsService.loadUserByUsername(username);
44 | UsernamePasswordAuthenticationToken authenticationToken =
45 | new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
46 |
47 | authenticationManager.authenticate(authenticationToken);
48 |
49 | if (authenticationToken.isAuthenticated()) {
50 | SecurityContextHolder.getContext().setAuthentication(authenticationToken);
51 |
52 | logger.debug(String.format("Successfully %s auto logged in", username));
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/service/UserDetailsServiceImpl.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.service;
2 |
3 | import net.proselyte.springsecurityapp.dao.UserDao;
4 | import net.proselyte.springsecurityapp.model.Role;
5 | import net.proselyte.springsecurityapp.model.User;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.security.core.GrantedAuthority;
8 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
9 | import org.springframework.security.core.userdetails.UserDetails;
10 | import org.springframework.security.core.userdetails.UserDetailsService;
11 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
12 | import org.springframework.transaction.annotation.Transactional;
13 |
14 | import java.util.HashSet;
15 | import java.util.Set;
16 |
17 | /**
18 | * Implementation of {@link org.springframework.security.core.userdetails.UserDetailsService} interface.
19 | *
20 | * @author Eugene Suleimanov
21 | * @version 1.0
22 | */
23 |
24 |
25 | public class UserDetailsServiceImpl implements UserDetailsService {
26 |
27 | @Autowired
28 | private UserDao userDao;
29 |
30 | @Override
31 | @Transactional(readOnly = true)
32 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
33 | User user = userDao.findByUsername(username);
34 |
35 | Set grantedAuthorities = new HashSet<>();
36 |
37 | for (Role role : user.getRoles()) {
38 | grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
39 | }
40 | return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/service/UserService.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.service;
2 |
3 | import net.proselyte.springsecurityapp.model.User;
4 |
5 | /**
6 | * Service class for {@link net.proselyte.springsecurityapp.model.User}
7 | *
8 | * @author Eugene Suleimanov
9 | * @version 1.0
10 | */
11 |
12 | public interface UserService {
13 |
14 | void save(User user);
15 |
16 | User findByUsername(String username);
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/service/UserServiceImpl.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.service;
2 |
3 | import net.proselyte.springsecurityapp.dao.RoleDao;
4 | import net.proselyte.springsecurityapp.dao.UserDao;
5 | import net.proselyte.springsecurityapp.model.Role;
6 | import net.proselyte.springsecurityapp.model.User;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
9 | import org.springframework.stereotype.Service;
10 |
11 | import java.util.HashSet;
12 | import java.util.Set;
13 |
14 | /**
15 | * Implementation of {@link UserService} interface.
16 | *
17 | * @author Eugene Suleimanov
18 | * @version 1.0
19 | */
20 |
21 | @Service
22 | public class UserServiceImpl implements UserService {
23 |
24 | @Autowired
25 | private UserDao userDao;
26 |
27 | @Autowired
28 | private RoleDao roleDao;
29 |
30 | @Autowired
31 | private BCryptPasswordEncoder bCryptPasswordEncoder;
32 |
33 | @Override
34 | public void save(User user) {
35 | user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
36 | Set roles = new HashSet<>();
37 | roles.add(roleDao.getOne(1L));
38 | user.setRoles(roles);
39 | userDao.save(user);
40 | }
41 |
42 | @Override
43 | public User findByUsername(String username) {
44 | return userDao.findByUsername(username);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/net/proselyte/springsecurityapp/validator/UserValidator.java:
--------------------------------------------------------------------------------
1 | package net.proselyte.springsecurityapp.validator;
2 |
3 | import net.proselyte.springsecurityapp.model.User;
4 | import net.proselyte.springsecurityapp.service.UserService;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Component;
7 | import org.springframework.validation.Errors;
8 | import org.springframework.validation.ValidationUtils;
9 | import org.springframework.validation.Validator;
10 |
11 | /**
12 | * Validator for {@link net.proselyte.springsecurityapp.model.User} class,
13 | * implements {@link Validator} interface.
14 | *
15 | * @author Eugene Suleimanov
16 | * @version 1.0
17 | */
18 |
19 | @Component
20 | public class UserValidator implements Validator {
21 |
22 | @Autowired
23 | private UserService userService;
24 |
25 | @Override
26 | public boolean supports(Class> aClass) {
27 | return User.class.equals(aClass);
28 | }
29 |
30 | @Override
31 | public void validate(Object o, Errors errors) {
32 | User user = (User) o;
33 |
34 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "Required");
35 | if (user.getUsername().length() < 8 || user.getUsername().length() > 32) {
36 | errors.rejectValue("username", "Size.userForm.username");
37 | }
38 |
39 | if (userService.findByUsername(user.getUsername()) != null) {
40 | errors.rejectValue("username", "Duplicate.userForm.username");
41 | }
42 |
43 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "Required");
44 | if (user.getPassword().length() < 8 || user.getPassword().length() > 32) {
45 | errors.rejectValue("password", "Size.userForm.password");
46 | }
47 |
48 | if (!user.getConfirmPassword().equals(user.getPassword())) {
49 | errors.rejectValue("confirmPassword", "Different.userForm.password");
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/resources/database.properties:
--------------------------------------------------------------------------------
1 | jdbc.driverClassName=com.mysql.jdbc.Driver
2 | jdbc.url=jdbc:mysql://localhost:3306/spring_security_app
3 | jdbc.username=root
4 | jdbc.password=root
--------------------------------------------------------------------------------
/src/main/resources/database.sql:
--------------------------------------------------------------------------------
1 | -- Table: users
2 | CREATE TABLE users (
3 | id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
4 | username VARCHAR(255) NOT NULL,
5 | password VARCHAR(255) NOT NULL
6 | )
7 | ENGINE = InnoDB;
8 |
9 | -- Table: roles
10 | CREATE TABLE roles (
11 | id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
12 | name VARCHAR(100) NOT NULL
13 | )
14 | ENGINE = InnoDB;
15 |
16 | -- Table for mapping user and roles: user_roles
17 | CREATE TABLE user_roles (
18 | user_id INT NOT NULL,
19 | role_id INT NOT NULL,
20 |
21 | FOREIGN KEY (user_id) REFERENCES users (id),
22 | FOREIGN KEY (role_id) REFERENCES roles (id),
23 |
24 | UNIQUE (user_id, role_id)
25 | )
26 | ENGINE = InnoDB;
27 |
28 | -- Insert data
29 |
30 | INSERT INTO users VALUES (1, 'proselyte', '$2a$11$uSXS6rLJ91WjgOHhEGDx..VGs7MkKZV68Lv5r1uwFu7HgtRn3dcXG');
31 |
32 | INSERT INTO roles VALUES (1, 'ROLE_USER');
33 | INSERT INTO roles VALUES (2, 'ROLE_ADMIN');
34 |
35 | INSERT INTO user_roles VALUES (1, 2);
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %date{HH:mm:ss.SSS} [%thread] %-5level %logger{15}#%line %msg\n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/resources/validation.properties:
--------------------------------------------------------------------------------
1 | Required=This field is required.
2 | Size.userForm.username=Username must be between 8 and 32 characters.
3 | Duplicate.userForm.username=Such username already exists.
4 | Size.userForm.password=Password must be over 8 characters.
5 | Different.userForm.password=Password don't match.
6 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/appconfig-data.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | org.hibernate.dialect.MySQL5Dialect
29 | true
30 |
31 |
32 |
33 |
34 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/appconfig-mvc.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | classpath:validation
15 |
16 |
17 |
18 |
19 |
20 |
21 | /WEB-INF/views/
22 |
23 |
24 | .jsp
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/appconfig-root.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/appconfig-security.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
30 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/views/admin.jsp:
--------------------------------------------------------------------------------
1 | <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Admin
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 | Admin Page ${pageContext.request.userPrincipal.name} | Logout
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/views/login.jsp:
--------------------------------------------------------------------------------
1 | <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3 | <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Log in with your account
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/views/registration.jsp:
--------------------------------------------------------------------------------
1 | <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3 | <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Create an account
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/views/welcome.jsp:
--------------------------------------------------------------------------------
1 | <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Welcome
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
28 | Welcome ${pageContext.request.userPrincipal.name} | Logout
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 | Spring Security Application
8 |
9 |
10 | contextConfigLocation
11 | /WEB-INF/appconfig-root.xml
12 |
13 |
14 |
15 | springSecurityFilterChain
16 | org.springframework.web.filter.DelegatingFilterProxy
17 |
18 |
19 | springSecurityFilterChain
20 | /*
21 |
22 |
23 |
24 | dispatcher
25 | org.springframework.web.servlet.DispatcherServlet
26 |
27 | contextConfigLocation
28 |
29 |
30 | 1
31 |
32 |
33 |
34 | dispatcher
35 | /
36 |
37 |
38 |
39 | org.springframework.web.context.ContextLoaderListener
40 |
41 |
--------------------------------------------------------------------------------
/src/main/webapp/resources/css/common.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 40px;
3 | padding-bottom: 40px;
4 | background-color: #eee;
5 | }
6 |
7 | .form-signin {
8 | max-width: 330px;
9 | padding: 15px;
10 | margin: 0 auto;
11 | }
12 |
13 | .form-signin .form-signin-heading,
14 | .form-signin .checkbox {
15 | margin-bottom: 10px;
16 | }
17 |
18 | .form-signin .checkbox {
19 | font-weight: normal;
20 | }
21 |
22 | .form-signin .form-control {
23 | position: relative;
24 | height: auto;
25 | -webkit-box-sizing: border-box;
26 | -moz-box-sizing: border-box;
27 | box-sizing: border-box;
28 | padding: 10px;
29 | font-size: 16px;
30 | }
31 |
32 | .form-signin .form-control:focus {
33 | z-index: 2;
34 | }
35 |
36 | .form-signin input {
37 | margin-top: 10px;
38 | border-bottom-right-radius: 0;
39 | border-bottom-left-radius: 0;
40 | }
41 |
42 | .form-signin button {
43 | margin-top: 10px;
44 | }
45 |
46 | .has-error {
47 | color: red
48 | }
--------------------------------------------------------------------------------
/src/main/webapp/resources/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.5 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under the MIT license
5 | */
6 | if ("undefined" == typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");
7 | +function (a) {
8 | "use strict";
9 | var b = a.fn.jquery.split(" ")[0].split(".");
10 | if (b[0] < 2 && b[1] < 9 || 1 == b[0] && 9 == b[1] && b[2] < 1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")
11 | }(jQuery), +function (a) {
12 | "use strict";
13 | function b() {
14 | var a = document.createElement("bootstrap"), b = {
15 | WebkitTransition: "webkitTransitionEnd",
16 | MozTransition: "transitionend",
17 | OTransition: "oTransitionEnd otransitionend",
18 | transition: "transitionend"
19 | };
20 | for (var c in b)if (void 0 !== a.style[c])return {end: b[c]};
21 | return !1
22 | }
23 |
24 | a.fn.emulateTransitionEnd = function (b) {
25 | var c = !1, d = this;
26 | a(this).one("bsTransitionEnd", function () {
27 | c = !0
28 | });
29 | var e = function () {
30 | c || a(d).trigger(a.support.transition.end)
31 | };
32 | return setTimeout(e, b), this
33 | }, a(function () {
34 | a.support.transition = b(), a.support.transition && (a.event.special.bsTransitionEnd = {
35 | bindType: a.support.transition.end,
36 | delegateType: a.support.transition.end,
37 | handle: function (b) {
38 | return a(b.target).is(this) ? b.handleObj.handler.apply(this, arguments) : void 0
39 | }
40 | })
41 | })
42 | }(jQuery), +function (a) {
43 | "use strict";
44 | function b(b) {
45 | return this.each(function () {
46 | var c = a(this), e = c.data("bs.alert");
47 | e || c.data("bs.alert", e = new d(this)), "string" == typeof b && e[b].call(c)
48 | })
49 | }
50 |
51 | var c = '[data-dismiss="alert"]', d = function (b) {
52 | a(b).on("click", c, this.close)
53 | };
54 | d.VERSION = "3.3.5", d.TRANSITION_DURATION = 150, d.prototype.close = function (b) {
55 | function c() {
56 | g.detach().trigger("closed.bs.alert").remove()
57 | }
58 |
59 | var e = a(this), f = e.attr("data-target");
60 | f || (f = e.attr("href"), f = f && f.replace(/.*(?=#[^\s]*$)/, ""));
61 | var g = a(f);
62 | b && b.preventDefault(), g.length || (g = e.closest(".alert")), g.trigger(b = a.Event("close.bs.alert")), b.isDefaultPrevented() || (g.removeClass("in"), a.support.transition && g.hasClass("fade") ? g.one("bsTransitionEnd", c).emulateTransitionEnd(d.TRANSITION_DURATION) : c())
63 | };
64 | var e = a.fn.alert;
65 | a.fn.alert = b, a.fn.alert.Constructor = d, a.fn.alert.noConflict = function () {
66 | return a.fn.alert = e, this
67 | }, a(document).on("click.bs.alert.data-api", c, d.prototype.close)
68 | }(jQuery), +function (a) {
69 | "use strict";
70 | function b(b) {
71 | return this.each(function () {
72 | var d = a(this), e = d.data("bs.button"), f = "object" == typeof b && b;
73 | e || d.data("bs.button", e = new c(this, f)), "toggle" == b ? e.toggle() : b && e.setState(b)
74 | })
75 | }
76 |
77 | var c = function (b, d) {
78 | this.$element = a(b), this.options = a.extend({}, c.DEFAULTS, d), this.isLoading = !1
79 | };
80 | c.VERSION = "3.3.5", c.DEFAULTS = {loadingText: "loading..."}, c.prototype.setState = function (b) {
81 | var c = "disabled", d = this.$element, e = d.is("input") ? "val" : "html", f = d.data();
82 | b += "Text", null == f.resetText && d.data("resetText", d[e]()), setTimeout(a.proxy(function () {
83 | d[e](null == f[b] ? this.options[b] : f[b]), "loadingText" == b ? (this.isLoading = !0, d.addClass(c).attr(c, c)) : this.isLoading && (this.isLoading = !1, d.removeClass(c).removeAttr(c))
84 | }, this), 0)
85 | }, c.prototype.toggle = function () {
86 | var a = !0, b = this.$element.closest('[data-toggle="buttons"]');
87 | if (b.length) {
88 | var c = this.$element.find("input");
89 | "radio" == c.prop("type") ? (c.prop("checked") && (a = !1), b.find(".active").removeClass("active"), this.$element.addClass("active")) : "checkbox" == c.prop("type") && (c.prop("checked") !== this.$element.hasClass("active") && (a = !1), this.$element.toggleClass("active")), c.prop("checked", this.$element.hasClass("active")), a && c.trigger("change")
90 | } else this.$element.attr("aria-pressed", !this.$element.hasClass("active")), this.$element.toggleClass("active")
91 | };
92 | var d = a.fn.button;
93 | a.fn.button = b, a.fn.button.Constructor = c, a.fn.button.noConflict = function () {
94 | return a.fn.button = d, this
95 | }, a(document).on("click.bs.button.data-api", '[data-toggle^="button"]', function (c) {
96 | var d = a(c.target);
97 | d.hasClass("btn") || (d = d.closest(".btn")), b.call(d, "toggle"), a(c.target).is('input[type="radio"]') || a(c.target).is('input[type="checkbox"]') || c.preventDefault()
98 | }).on("focus.bs.button.data-api blur.bs.button.data-api", '[data-toggle^="button"]', function (b) {
99 | a(b.target).closest(".btn").toggleClass("focus", /^focus(in)?$/.test(b.type))
100 | })
101 | }(jQuery), +function (a) {
102 | "use strict";
103 | function b(b) {
104 | return this.each(function () {
105 | var d = a(this), e = d.data("bs.carousel"), f = a.extend({}, c.DEFAULTS, d.data(), "object" == typeof b && b), g = "string" == typeof b ? b : f.slide;
106 | e || d.data("bs.carousel", e = new c(this, f)), "number" == typeof b ? e.to(b) : g ? e[g]() : f.interval && e.pause().cycle()
107 | })
108 | }
109 |
110 | var c = function (b, c) {
111 | this.$element = a(b), this.$indicators = this.$element.find(".carousel-indicators"), this.options = c, this.paused = null, this.sliding = null, this.interval = null, this.$active = null, this.$items = null, this.options.keyboard && this.$element.on("keydown.bs.carousel", a.proxy(this.keydown, this)), "hover" == this.options.pause && !("ontouchstart"in document.documentElement) && this.$element.on("mouseenter.bs.carousel", a.proxy(this.pause, this)).on("mouseleave.bs.carousel", a.proxy(this.cycle, this))
112 | };
113 | c.VERSION = "3.3.5", c.TRANSITION_DURATION = 600, c.DEFAULTS = {
114 | interval: 5e3,
115 | pause: "hover",
116 | wrap: !0,
117 | keyboard: !0
118 | }, c.prototype.keydown = function (a) {
119 | if (!/input|textarea/i.test(a.target.tagName)) {
120 | switch (a.which) {
121 | case 37:
122 | this.prev();
123 | break;
124 | case 39:
125 | this.next();
126 | break;
127 | default:
128 | return
129 | }
130 | a.preventDefault()
131 | }
132 | }, c.prototype.cycle = function (b) {
133 | return b || (this.paused = !1), this.interval && clearInterval(this.interval), this.options.interval && !this.paused && (this.interval = setInterval(a.proxy(this.next, this), this.options.interval)), this
134 | }, c.prototype.getItemIndex = function (a) {
135 | return this.$items = a.parent().children(".item"), this.$items.index(a || this.$active)
136 | }, c.prototype.getItemForDirection = function (a, b) {
137 | var c = this.getItemIndex(b), d = "prev" == a && 0 === c || "next" == a && c == this.$items.length - 1;
138 | if (d && !this.options.wrap)return b;
139 | var e = "prev" == a ? -1 : 1, f = (c + e) % this.$items.length;
140 | return this.$items.eq(f)
141 | }, c.prototype.to = function (a) {
142 | var b = this, c = this.getItemIndex(this.$active = this.$element.find(".item.active"));
143 | return a > this.$items.length - 1 || 0 > a ? void 0 : this.sliding ? this.$element.one("slid.bs.carousel", function () {
144 | b.to(a)
145 | }) : c == a ? this.pause().cycle() : this.slide(a > c ? "next" : "prev", this.$items.eq(a))
146 | }, c.prototype.pause = function (b) {
147 | return b || (this.paused = !0), this.$element.find(".next, .prev").length && a.support.transition && (this.$element.trigger(a.support.transition.end), this.cycle(!0)), this.interval = clearInterval(this.interval), this
148 | }, c.prototype.next = function () {
149 | return this.sliding ? void 0 : this.slide("next")
150 | }, c.prototype.prev = function () {
151 | return this.sliding ? void 0 : this.slide("prev")
152 | }, c.prototype.slide = function (b, d) {
153 | var e = this.$element.find(".item.active"), f = d || this.getItemForDirection(b, e), g = this.interval, h = "next" == b ? "left" : "right", i = this;
154 | if (f.hasClass("active"))return this.sliding = !1;
155 | var j = f[0], k = a.Event("slide.bs.carousel", {relatedTarget: j, direction: h});
156 | if (this.$element.trigger(k), !k.isDefaultPrevented()) {
157 | if (this.sliding = !0, g && this.pause(), this.$indicators.length) {
158 | this.$indicators.find(".active").removeClass("active");
159 | var l = a(this.$indicators.children()[this.getItemIndex(f)]);
160 | l && l.addClass("active")
161 | }
162 | var m = a.Event("slid.bs.carousel", {relatedTarget: j, direction: h});
163 | return a.support.transition && this.$element.hasClass("slide") ? (f.addClass(b), f[0].offsetWidth, e.addClass(h), f.addClass(h), e.one("bsTransitionEnd", function () {
164 | f.removeClass([b, h].join(" ")).addClass("active"), e.removeClass(["active", h].join(" ")), i.sliding = !1, setTimeout(function () {
165 | i.$element.trigger(m)
166 | }, 0)
167 | }).emulateTransitionEnd(c.TRANSITION_DURATION)) : (e.removeClass("active"), f.addClass("active"), this.sliding = !1, this.$element.trigger(m)), g && this.cycle(), this
168 | }
169 | };
170 | var d = a.fn.carousel;
171 | a.fn.carousel = b, a.fn.carousel.Constructor = c, a.fn.carousel.noConflict = function () {
172 | return a.fn.carousel = d, this
173 | };
174 | var e = function (c) {
175 | var d, e = a(this), f = a(e.attr("data-target") || (d = e.attr("href")) && d.replace(/.*(?=#[^\s]+$)/, ""));
176 | if (f.hasClass("carousel")) {
177 | var g = a.extend({}, f.data(), e.data()), h = e.attr("data-slide-to");
178 | h && (g.interval = !1), b.call(f, g), h && f.data("bs.carousel").to(h), c.preventDefault()
179 | }
180 | };
181 | a(document).on("click.bs.carousel.data-api", "[data-slide]", e).on("click.bs.carousel.data-api", "[data-slide-to]", e), a(window).on("load", function () {
182 | a('[data-ride="carousel"]').each(function () {
183 | var c = a(this);
184 | b.call(c, c.data())
185 | })
186 | })
187 | }(jQuery), +function (a) {
188 | "use strict";
189 | function b(b) {
190 | var c, d = b.attr("data-target") || (c = b.attr("href")) && c.replace(/.*(?=#[^\s]+$)/, "");
191 | return a(d)
192 | }
193 |
194 | function c(b) {
195 | return this.each(function () {
196 | var c = a(this), e = c.data("bs.collapse"), f = a.extend({}, d.DEFAULTS, c.data(), "object" == typeof b && b);
197 | !e && f.toggle && /show|hide/.test(b) && (f.toggle = !1), e || c.data("bs.collapse", e = new d(this, f)), "string" == typeof b && e[b]()
198 | })
199 | }
200 |
201 | var d = function (b, c) {
202 | this.$element = a(b), this.options = a.extend({}, d.DEFAULTS, c), this.$trigger = a('[data-toggle="collapse"][href="#' + b.id + '"],[data-toggle="collapse"][data-target="#' + b.id + '"]'), this.transitioning = null, this.options.parent ? this.$parent = this.getParent() : this.addAriaAndCollapsedClass(this.$element, this.$trigger), this.options.toggle && this.toggle()
203 | };
204 | d.VERSION = "3.3.5", d.TRANSITION_DURATION = 350, d.DEFAULTS = {toggle: !0}, d.prototype.dimension = function () {
205 | var a = this.$element.hasClass("width");
206 | return a ? "width" : "height"
207 | }, d.prototype.show = function () {
208 | if (!this.transitioning && !this.$element.hasClass("in")) {
209 | var b, e = this.$parent && this.$parent.children(".panel").children(".in, .collapsing");
210 | if (!(e && e.length && (b = e.data("bs.collapse"), b && b.transitioning))) {
211 | var f = a.Event("show.bs.collapse");
212 | if (this.$element.trigger(f), !f.isDefaultPrevented()) {
213 | e && e.length && (c.call(e, "hide"), b || e.data("bs.collapse", null));
214 | var g = this.dimension();
215 | this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded", !0), this.$trigger.removeClass("collapsed").attr("aria-expanded", !0), this.transitioning = 1;
216 | var h = function () {
217 | this.$element.removeClass("collapsing").addClass("collapse in")[g](""), this.transitioning = 0, this.$element.trigger("shown.bs.collapse")
218 | };
219 | if (!a.support.transition)return h.call(this);
220 | var i = a.camelCase(["scroll", g].join("-"));
221 | this.$element.one("bsTransitionEnd", a.proxy(h, this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])
222 | }
223 | }
224 | }
225 | }, d.prototype.hide = function () {
226 | if (!this.transitioning && this.$element.hasClass("in")) {
227 | var b = a.Event("hide.bs.collapse");
228 | if (this.$element.trigger(b), !b.isDefaultPrevented()) {
229 | var c = this.dimension();
230 | this.$element[c](this.$element[c]())[0].offsetHeight, this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded", !1), this.$trigger.addClass("collapsed").attr("aria-expanded", !1), this.transitioning = 1;
231 | var e = function () {
232 | this.transitioning = 0, this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")
233 | };
234 | return a.support.transition ? void this.$element[c](0).one("bsTransitionEnd", a.proxy(e, this)).emulateTransitionEnd(d.TRANSITION_DURATION) : e.call(this)
235 | }
236 | }
237 | }, d.prototype.toggle = function () {
238 | this[this.$element.hasClass("in") ? "hide" : "show"]()
239 | }, d.prototype.getParent = function () {
240 | return a(this.options.parent).find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]').each(a.proxy(function (c, d) {
241 | var e = a(d);
242 | this.addAriaAndCollapsedClass(b(e), e)
243 | }, this)).end()
244 | }, d.prototype.addAriaAndCollapsedClass = function (a, b) {
245 | var c = a.hasClass("in");
246 | a.attr("aria-expanded", c), b.toggleClass("collapsed", !c).attr("aria-expanded", c)
247 | };
248 | var e = a.fn.collapse;
249 | a.fn.collapse = c, a.fn.collapse.Constructor = d, a.fn.collapse.noConflict = function () {
250 | return a.fn.collapse = e, this
251 | }, a(document).on("click.bs.collapse.data-api", '[data-toggle="collapse"]', function (d) {
252 | var e = a(this);
253 | e.attr("data-target") || d.preventDefault();
254 | var f = b(e), g = f.data("bs.collapse"), h = g ? "toggle" : e.data();
255 | c.call(f, h)
256 | })
257 | }(jQuery), +function (a) {
258 | "use strict";
259 | function b(b) {
260 | var c = b.attr("data-target");
261 | c || (c = b.attr("href"), c = c && /#[A-Za-z]/.test(c) && c.replace(/.*(?=#[^\s]*$)/, ""));
262 | var d = c && a(c);
263 | return d && d.length ? d : b.parent()
264 | }
265 |
266 | function c(c) {
267 | c && 3 === c.which || (a(e).remove(), a(f).each(function () {
268 | var d = a(this), e = b(d), f = {relatedTarget: this};
269 | e.hasClass("open") && (c && "click" == c.type && /input|textarea/i.test(c.target.tagName) && a.contains(e[0], c.target) || (e.trigger(c = a.Event("hide.bs.dropdown", f)), c.isDefaultPrevented() || (d.attr("aria-expanded", "false"), e.removeClass("open").trigger("hidden.bs.dropdown", f))))
270 | }))
271 | }
272 |
273 | function d(b) {
274 | return this.each(function () {
275 | var c = a(this), d = c.data("bs.dropdown");
276 | d || c.data("bs.dropdown", d = new g(this)), "string" == typeof b && d[b].call(c)
277 | })
278 | }
279 |
280 | var e = ".dropdown-backdrop", f = '[data-toggle="dropdown"]', g = function (b) {
281 | a(b).on("click.bs.dropdown", this.toggle)
282 | };
283 | g.VERSION = "3.3.5", g.prototype.toggle = function (d) {
284 | var e = a(this);
285 | if (!e.is(".disabled, :disabled")) {
286 | var f = b(e), g = f.hasClass("open");
287 | if (c(), !g) {
288 | "ontouchstart"in document.documentElement && !f.closest(".navbar-nav").length && a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click", c);
289 | var h = {relatedTarget: this};
290 | if (f.trigger(d = a.Event("show.bs.dropdown", h)), d.isDefaultPrevented())return;
291 | e.trigger("focus").attr("aria-expanded", "true"), f.toggleClass("open").trigger("shown.bs.dropdown", h)
292 | }
293 | return !1
294 | }
295 | }, g.prototype.keydown = function (c) {
296 | if (/(38|40|27|32)/.test(c.which) && !/input|textarea/i.test(c.target.tagName)) {
297 | var d = a(this);
298 | if (c.preventDefault(), c.stopPropagation(), !d.is(".disabled, :disabled")) {
299 | var e = b(d), g = e.hasClass("open");
300 | if (!g && 27 != c.which || g && 27 == c.which)return 27 == c.which && e.find(f).trigger("focus"), d.trigger("click");
301 | var h = " li:not(.disabled):visible a", i = e.find(".dropdown-menu" + h);
302 | if (i.length) {
303 | var j = i.index(c.target);
304 | 38 == c.which && j > 0 && j--, 40 == c.which && j < i.length - 1 && j++, ~j || (j = 0), i.eq(j).trigger("focus")
305 | }
306 | }
307 | }
308 | };
309 | var h = a.fn.dropdown;
310 | a.fn.dropdown = d, a.fn.dropdown.Constructor = g, a.fn.dropdown.noConflict = function () {
311 | return a.fn.dropdown = h, this
312 | }, a(document).on("click.bs.dropdown.data-api", c).on("click.bs.dropdown.data-api", ".dropdown form", function (a) {
313 | a.stopPropagation()
314 | }).on("click.bs.dropdown.data-api", f, g.prototype.toggle).on("keydown.bs.dropdown.data-api", f, g.prototype.keydown).on("keydown.bs.dropdown.data-api", ".dropdown-menu", g.prototype.keydown)
315 | }(jQuery), +function (a) {
316 | "use strict";
317 | function b(b, d) {
318 | return this.each(function () {
319 | var e = a(this), f = e.data("bs.modal"), g = a.extend({}, c.DEFAULTS, e.data(), "object" == typeof b && b);
320 | f || e.data("bs.modal", f = new c(this, g)), "string" == typeof b ? f[b](d) : g.show && f.show(d)
321 | })
322 | }
323 |
324 | var c = function (b, c) {
325 | this.options = c, this.$body = a(document.body), this.$element = a(b), this.$dialog = this.$element.find(".modal-dialog"), this.$backdrop = null, this.isShown = null, this.originalBodyPad = null, this.scrollbarWidth = 0, this.ignoreBackdropClick = !1, this.options.remote && this.$element.find(".modal-content").load(this.options.remote, a.proxy(function () {
326 | this.$element.trigger("loaded.bs.modal")
327 | }, this))
328 | };
329 | c.VERSION = "3.3.5", c.TRANSITION_DURATION = 300, c.BACKDROP_TRANSITION_DURATION = 150, c.DEFAULTS = {
330 | backdrop: !0,
331 | keyboard: !0,
332 | show: !0
333 | }, c.prototype.toggle = function (a) {
334 | return this.isShown ? this.hide() : this.show(a)
335 | }, c.prototype.show = function (b) {
336 | var d = this, e = a.Event("show.bs.modal", {relatedTarget: b});
337 | this.$element.trigger(e), this.isShown || e.isDefaultPrevented() || (this.isShown = !0, this.checkScrollbar(), this.setScrollbar(), this.$body.addClass("modal-open"), this.escape(), this.resize(), this.$element.on("click.dismiss.bs.modal", '[data-dismiss="modal"]', a.proxy(this.hide, this)), this.$dialog.on("mousedown.dismiss.bs.modal", function () {
338 | d.$element.one("mouseup.dismiss.bs.modal", function (b) {
339 | a(b.target).is(d.$element) && (d.ignoreBackdropClick = !0)
340 | })
341 | }), this.backdrop(function () {
342 | var e = a.support.transition && d.$element.hasClass("fade");
343 | d.$element.parent().length || d.$element.appendTo(d.$body), d.$element.show().scrollTop(0), d.adjustDialog(), e && d.$element[0].offsetWidth, d.$element.addClass("in"), d.enforceFocus();
344 | var f = a.Event("shown.bs.modal", {relatedTarget: b});
345 | e ? d.$dialog.one("bsTransitionEnd", function () {
346 | d.$element.trigger("focus").trigger(f)
347 | }).emulateTransitionEnd(c.TRANSITION_DURATION) : d.$element.trigger("focus").trigger(f)
348 | }))
349 | }, c.prototype.hide = function (b) {
350 | b && b.preventDefault(), b = a.Event("hide.bs.modal"), this.$element.trigger(b), this.isShown && !b.isDefaultPrevented() && (this.isShown = !1, this.escape(), this.resize(), a(document).off("focusin.bs.modal"), this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"), this.$dialog.off("mousedown.dismiss.bs.modal"), a.support.transition && this.$element.hasClass("fade") ? this.$element.one("bsTransitionEnd", a.proxy(this.hideModal, this)).emulateTransitionEnd(c.TRANSITION_DURATION) : this.hideModal())
351 | }, c.prototype.enforceFocus = function () {
352 | a(document).off("focusin.bs.modal").on("focusin.bs.modal", a.proxy(function (a) {
353 | this.$element[0] === a.target || this.$element.has(a.target).length || this.$element.trigger("focus")
354 | }, this))
355 | }, c.prototype.escape = function () {
356 | this.isShown && this.options.keyboard ? this.$element.on("keydown.dismiss.bs.modal", a.proxy(function (a) {
357 | 27 == a.which && this.hide()
358 | }, this)) : this.isShown || this.$element.off("keydown.dismiss.bs.modal")
359 | }, c.prototype.resize = function () {
360 | this.isShown ? a(window).on("resize.bs.modal", a.proxy(this.handleUpdate, this)) : a(window).off("resize.bs.modal")
361 | }, c.prototype.hideModal = function () {
362 | var a = this;
363 | this.$element.hide(), this.backdrop(function () {
364 | a.$body.removeClass("modal-open"), a.resetAdjustments(), a.resetScrollbar(), a.$element.trigger("hidden.bs.modal")
365 | })
366 | }, c.prototype.removeBackdrop = function () {
367 | this.$backdrop && this.$backdrop.remove(), this.$backdrop = null
368 | }, c.prototype.backdrop = function (b) {
369 | var d = this, e = this.$element.hasClass("fade") ? "fade" : "";
370 | if (this.isShown && this.options.backdrop) {
371 | var f = a.support.transition && e;
372 | if (this.$backdrop = a(document.createElement("div")).addClass("modal-backdrop " + e).appendTo(this.$body), this.$element.on("click.dismiss.bs.modal", a.proxy(function (a) {
373 | return this.ignoreBackdropClick ? void(this.ignoreBackdropClick = !1) : void(a.target === a.currentTarget && ("static" == this.options.backdrop ? this.$element[0].focus() : this.hide()))
374 | }, this)), f && this.$backdrop[0].offsetWidth, this.$backdrop.addClass("in"), !b)return;
375 | f ? this.$backdrop.one("bsTransitionEnd", b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION) : b()
376 | } else if (!this.isShown && this.$backdrop) {
377 | this.$backdrop.removeClass("in");
378 | var g = function () {
379 | d.removeBackdrop(), b && b()
380 | };
381 | a.support.transition && this.$element.hasClass("fade") ? this.$backdrop.one("bsTransitionEnd", g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION) : g()
382 | } else b && b()
383 | }, c.prototype.handleUpdate = function () {
384 | this.adjustDialog()
385 | }, c.prototype.adjustDialog = function () {
386 | var a = this.$element[0].scrollHeight > document.documentElement.clientHeight;
387 | this.$element.css({
388 | paddingLeft: !this.bodyIsOverflowing && a ? this.scrollbarWidth : "",
389 | paddingRight: this.bodyIsOverflowing && !a ? this.scrollbarWidth : ""
390 | })
391 | }, c.prototype.resetAdjustments = function () {
392 | this.$element.css({paddingLeft: "", paddingRight: ""})
393 | }, c.prototype.checkScrollbar = function () {
394 | var a = window.innerWidth;
395 | if (!a) {
396 | var b = document.documentElement.getBoundingClientRect();
397 | a = b.right - Math.abs(b.left)
398 | }
399 | this.bodyIsOverflowing = document.body.clientWidth < a, this.scrollbarWidth = this.measureScrollbar()
400 | }, c.prototype.setScrollbar = function () {
401 | var a = parseInt(this.$body.css("padding-right") || 0, 10);
402 | this.originalBodyPad = document.body.style.paddingRight || "", this.bodyIsOverflowing && this.$body.css("padding-right", a + this.scrollbarWidth)
403 | }, c.prototype.resetScrollbar = function () {
404 | this.$body.css("padding-right", this.originalBodyPad)
405 | }, c.prototype.measureScrollbar = function () {
406 | var a = document.createElement("div");
407 | a.className = "modal-scrollbar-measure", this.$body.append(a);
408 | var b = a.offsetWidth - a.clientWidth;
409 | return this.$body[0].removeChild(a), b
410 | };
411 | var d = a.fn.modal;
412 | a.fn.modal = b, a.fn.modal.Constructor = c, a.fn.modal.noConflict = function () {
413 | return a.fn.modal = d, this
414 | }, a(document).on("click.bs.modal.data-api", '[data-toggle="modal"]', function (c) {
415 | var d = a(this), e = d.attr("href"), f = a(d.attr("data-target") || e && e.replace(/.*(?=#[^\s]+$)/, "")), g = f.data("bs.modal") ? "toggle" : a.extend({remote: !/#/.test(e) && e}, f.data(), d.data());
416 | d.is("a") && c.preventDefault(), f.one("show.bs.modal", function (a) {
417 | a.isDefaultPrevented() || f.one("hidden.bs.modal", function () {
418 | d.is(":visible") && d.trigger("focus")
419 | })
420 | }), b.call(f, g, this)
421 | })
422 | }(jQuery), +function (a) {
423 | "use strict";
424 | function b(b) {
425 | return this.each(function () {
426 | var d = a(this), e = d.data("bs.tooltip"), f = "object" == typeof b && b;
427 | (e || !/destroy|hide/.test(b)) && (e || d.data("bs.tooltip", e = new c(this, f)), "string" == typeof b && e[b]())
428 | })
429 | }
430 |
431 | var c = function (a, b) {
432 | this.type = null, this.options = null, this.enabled = null, this.timeout = null, this.hoverState = null, this.$element = null, this.inState = null, this.init("tooltip", a, b)
433 | };
434 | c.VERSION = "3.3.5", c.TRANSITION_DURATION = 150, c.DEFAULTS = {
435 | animation: !0,
436 | placement: "top",
437 | selector: !1,
438 | template: '',
439 | trigger: "hover focus",
440 | title: "",
441 | delay: 0,
442 | html: !1,
443 | container: !1,
444 | viewport: {selector: "body", padding: 0}
445 | }, c.prototype.init = function (b, c, d) {
446 | if (this.enabled = !0, this.type = b, this.$element = a(c), this.options = this.getOptions(d), this.$viewport = this.options.viewport && a(a.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : this.options.viewport.selector || this.options.viewport), this.inState = {
447 | click: !1,
448 | hover: !1,
449 | focus: !1
450 | }, this.$element[0]instanceof document.constructor && !this.options.selector)throw new Error("`selector` option must be specified when initializing " + this.type + " on the window.document object!");
451 | for (var e = this.options.trigger.split(" "), f = e.length; f--;) {
452 | var g = e[f];
453 | if ("click" == g)this.$element.on("click." + this.type, this.options.selector, a.proxy(this.toggle, this)); else if ("manual" != g) {
454 | var h = "hover" == g ? "mouseenter" : "focusin", i = "hover" == g ? "mouseleave" : "focusout";
455 | this.$element.on(h + "." + this.type, this.options.selector, a.proxy(this.enter, this)), this.$element.on(i + "." + this.type, this.options.selector, a.proxy(this.leave, this))
456 | }
457 | }
458 | this.options.selector ? this._options = a.extend({}, this.options, {
459 | trigger: "manual",
460 | selector: ""
461 | }) : this.fixTitle()
462 | }, c.prototype.getDefaults = function () {
463 | return c.DEFAULTS
464 | }, c.prototype.getOptions = function (b) {
465 | return b = a.extend({}, this.getDefaults(), this.$element.data(), b), b.delay && "number" == typeof b.delay && (b.delay = {
466 | show: b.delay,
467 | hide: b.delay
468 | }), b
469 | }, c.prototype.getDelegateOptions = function () {
470 | var b = {}, c = this.getDefaults();
471 | return this._options && a.each(this._options, function (a, d) {
472 | c[a] != d && (b[a] = d)
473 | }), b
474 | }, c.prototype.enter = function (b) {
475 | var c = b instanceof this.constructor ? b : a(b.currentTarget).data("bs." + this.type);
476 | return c || (c = new this.constructor(b.currentTarget, this.getDelegateOptions()), a(b.currentTarget).data("bs." + this.type, c)), b instanceof a.Event && (c.inState["focusin" == b.type ? "focus" : "hover"] = !0), c.tip().hasClass("in") || "in" == c.hoverState ? void(c.hoverState = "in") : (clearTimeout(c.timeout), c.hoverState = "in", c.options.delay && c.options.delay.show ? void(c.timeout = setTimeout(function () {
477 | "in" == c.hoverState && c.show()
478 | }, c.options.delay.show)) : c.show())
479 | }, c.prototype.isInStateTrue = function () {
480 | for (var a in this.inState)if (this.inState[a])return !0;
481 | return !1
482 | }, c.prototype.leave = function (b) {
483 | var c = b instanceof this.constructor ? b : a(b.currentTarget).data("bs." + this.type);
484 | return c || (c = new this.constructor(b.currentTarget, this.getDelegateOptions()), a(b.currentTarget).data("bs." + this.type, c)), b instanceof a.Event && (c.inState["focusout" == b.type ? "focus" : "hover"] = !1), c.isInStateTrue() ? void 0 : (clearTimeout(c.timeout), c.hoverState = "out", c.options.delay && c.options.delay.hide ? void(c.timeout = setTimeout(function () {
485 | "out" == c.hoverState && c.hide()
486 | }, c.options.delay.hide)) : c.hide())
487 | }, c.prototype.show = function () {
488 | var b = a.Event("show.bs." + this.type);
489 | if (this.hasContent() && this.enabled) {
490 | this.$element.trigger(b);
491 | var d = a.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]);
492 | if (b.isDefaultPrevented() || !d)return;
493 | var e = this, f = this.tip(), g = this.getUID(this.type);
494 | this.setContent(), f.attr("id", g), this.$element.attr("aria-describedby", g), this.options.animation && f.addClass("fade");
495 | var h = "function" == typeof this.options.placement ? this.options.placement.call(this, f[0], this.$element[0]) : this.options.placement, i = /\s?auto?\s?/i, j = i.test(h);
496 | j && (h = h.replace(i, "") || "top"), f.detach().css({
497 | top: 0,
498 | left: 0,
499 | display: "block"
500 | }).addClass(h).data("bs." + this.type, this), this.options.container ? f.appendTo(this.options.container) : f.insertAfter(this.$element), this.$element.trigger("inserted.bs." + this.type);
501 | var k = this.getPosition(), l = f[0].offsetWidth, m = f[0].offsetHeight;
502 | if (j) {
503 | var n = h, o = this.getPosition(this.$viewport);
504 | h = "bottom" == h && k.bottom + m > o.bottom ? "top" : "top" == h && k.top - m < o.top ? "bottom" : "right" == h && k.right + l > o.width ? "left" : "left" == h && k.left - l < o.left ? "right" : h, f.removeClass(n).addClass(h)
505 | }
506 | var p = this.getCalculatedOffset(h, k, l, m);
507 | this.applyPlacement(p, h);
508 | var q = function () {
509 | var a = e.hoverState;
510 | e.$element.trigger("shown.bs." + e.type), e.hoverState = null, "out" == a && e.leave(e)
511 | };
512 | a.support.transition && this.$tip.hasClass("fade") ? f.one("bsTransitionEnd", q).emulateTransitionEnd(c.TRANSITION_DURATION) : q()
513 | }
514 | }, c.prototype.applyPlacement = function (b, c) {
515 | var d = this.tip(), e = d[0].offsetWidth, f = d[0].offsetHeight, g = parseInt(d.css("margin-top"), 10), h = parseInt(d.css("margin-left"), 10);
516 | isNaN(g) && (g = 0), isNaN(h) && (h = 0), b.top += g, b.left += h, a.offset.setOffset(d[0], a.extend({
517 | using: function (a) {
518 | d.css({top: Math.round(a.top), left: Math.round(a.left)})
519 | }
520 | }, b), 0), d.addClass("in");
521 | var i = d[0].offsetWidth, j = d[0].offsetHeight;
522 | "top" == c && j != f && (b.top = b.top + f - j);
523 | var k = this.getViewportAdjustedDelta(c, b, i, j);
524 | k.left ? b.left += k.left : b.top += k.top;
525 | var l = /top|bottom/.test(c), m = l ? 2 * k.left - e + i : 2 * k.top - f + j, n = l ? "offsetWidth" : "offsetHeight";
526 | d.offset(b), this.replaceArrow(m, d[0][n], l)
527 | }, c.prototype.replaceArrow = function (a, b, c) {
528 | this.arrow().css(c ? "left" : "top", 50 * (1 - a / b) + "%").css(c ? "top" : "left", "")
529 | }, c.prototype.setContent = function () {
530 | var a = this.tip(), b = this.getTitle();
531 | a.find(".tooltip-inner")[this.options.html ? "html" : "text"](b), a.removeClass("fade in top bottom left right")
532 | }, c.prototype.hide = function (b) {
533 | function d() {
534 | "in" != e.hoverState && f.detach(), e.$element.removeAttr("aria-describedby").trigger("hidden.bs." + e.type), b && b()
535 | }
536 |
537 | var e = this, f = a(this.$tip), g = a.Event("hide.bs." + this.type);
538 | return this.$element.trigger(g), g.isDefaultPrevented() ? void 0 : (f.removeClass("in"), a.support.transition && f.hasClass("fade") ? f.one("bsTransitionEnd", d).emulateTransitionEnd(c.TRANSITION_DURATION) : d(), this.hoverState = null, this)
539 | }, c.prototype.fixTitle = function () {
540 | var a = this.$element;
541 | (a.attr("title") || "string" != typeof a.attr("data-original-title")) && a.attr("data-original-title", a.attr("title") || "").attr("title", "")
542 | }, c.prototype.hasContent = function () {
543 | return this.getTitle()
544 | }, c.prototype.getPosition = function (b) {
545 | b = b || this.$element;
546 | var c = b[0], d = "BODY" == c.tagName, e = c.getBoundingClientRect();
547 | null == e.width && (e = a.extend({}, e, {width: e.right - e.left, height: e.bottom - e.top}));
548 | var f = d ? {
549 | top: 0,
550 | left: 0
551 | } : b.offset(), g = {scroll: d ? document.documentElement.scrollTop || document.body.scrollTop : b.scrollTop()}, h = d ? {
552 | width: a(window).width(),
553 | height: a(window).height()
554 | } : null;
555 | return a.extend({}, e, g, h, f)
556 | }, c.prototype.getCalculatedOffset = function (a, b, c, d) {
557 | return "bottom" == a ? {
558 | top: b.top + b.height,
559 | left: b.left + b.width / 2 - c / 2
560 | } : "top" == a ? {
561 | top: b.top - d,
562 | left: b.left + b.width / 2 - c / 2
563 | } : "left" == a ? {top: b.top + b.height / 2 - d / 2, left: b.left - c} : {
564 | top: b.top + b.height / 2 - d / 2,
565 | left: b.left + b.width
566 | }
567 | }, c.prototype.getViewportAdjustedDelta = function (a, b, c, d) {
568 | var e = {top: 0, left: 0};
569 | if (!this.$viewport)return e;
570 | var f = this.options.viewport && this.options.viewport.padding || 0, g = this.getPosition(this.$viewport);
571 | if (/right|left/.test(a)) {
572 | var h = b.top - f - g.scroll, i = b.top + f - g.scroll + d;
573 | h < g.top ? e.top = g.top - h : i > g.top + g.height && (e.top = g.top + g.height - i)
574 | } else {
575 | var j = b.left - f, k = b.left + f + c;
576 | j < g.left ? e.left = g.left - j : k > g.right && (e.left = g.left + g.width - k)
577 | }
578 | return e
579 | }, c.prototype.getTitle = function () {
580 | var a, b = this.$element, c = this.options;
581 | return a = b.attr("data-original-title") || ("function" == typeof c.title ? c.title.call(b[0]) : c.title)
582 | }, c.prototype.getUID = function (a) {
583 | do a += ~~(1e6 * Math.random()); while (document.getElementById(a));
584 | return a
585 | }, c.prototype.tip = function () {
586 | if (!this.$tip && (this.$tip = a(this.options.template), 1 != this.$tip.length))throw new Error(this.type + " `template` option must consist of exactly 1 top-level element!");
587 | return this.$tip
588 | }, c.prototype.arrow = function () {
589 | return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
590 | }, c.prototype.enable = function () {
591 | this.enabled = !0
592 | }, c.prototype.disable = function () {
593 | this.enabled = !1
594 | }, c.prototype.toggleEnabled = function () {
595 | this.enabled = !this.enabled
596 | }, c.prototype.toggle = function (b) {
597 | var c = this;
598 | b && (c = a(b.currentTarget).data("bs." + this.type), c || (c = new this.constructor(b.currentTarget, this.getDelegateOptions()), a(b.currentTarget).data("bs." + this.type, c))), b ? (c.inState.click = !c.inState.click, c.isInStateTrue() ? c.enter(c) : c.leave(c)) : c.tip().hasClass("in") ? c.leave(c) : c.enter(c)
599 | }, c.prototype.destroy = function () {
600 | var a = this;
601 | clearTimeout(this.timeout), this.hide(function () {
602 | a.$element.off("." + a.type).removeData("bs." + a.type), a.$tip && a.$tip.detach(), a.$tip = null, a.$arrow = null, a.$viewport = null
603 | })
604 | };
605 | var d = a.fn.tooltip;
606 | a.fn.tooltip = b, a.fn.tooltip.Constructor = c, a.fn.tooltip.noConflict = function () {
607 | return a.fn.tooltip = d, this
608 | }
609 | }(jQuery), +function (a) {
610 | "use strict";
611 | function b(b) {
612 | return this.each(function () {
613 | var d = a(this), e = d.data("bs.popover"), f = "object" == typeof b && b;
614 | (e || !/destroy|hide/.test(b)) && (e || d.data("bs.popover", e = new c(this, f)), "string" == typeof b && e[b]())
615 | })
616 | }
617 |
618 | var c = function (a, b) {
619 | this.init("popover", a, b)
620 | };
621 | if (!a.fn.tooltip)throw new Error("Popover requires tooltip.js");
622 | c.VERSION = "3.3.5", c.DEFAULTS = a.extend({}, a.fn.tooltip.Constructor.DEFAULTS, {
623 | placement: "right",
624 | trigger: "click",
625 | content: "",
626 | template: ''
627 | }), c.prototype = a.extend({}, a.fn.tooltip.Constructor.prototype), c.prototype.constructor = c, c.prototype.getDefaults = function () {
628 | return c.DEFAULTS
629 | }, c.prototype.setContent = function () {
630 | var a = this.tip(), b = this.getTitle(), c = this.getContent();
631 | a.find(".popover-title")[this.options.html ? "html" : "text"](b), a.find(".popover-content").children().detach().end()[this.options.html ? "string" == typeof c ? "html" : "append" : "text"](c), a.removeClass("fade top bottom left right in"), a.find(".popover-title").html() || a.find(".popover-title").hide()
632 | }, c.prototype.hasContent = function () {
633 | return this.getTitle() || this.getContent()
634 | }, c.prototype.getContent = function () {
635 | var a = this.$element, b = this.options;
636 | return a.attr("data-content") || ("function" == typeof b.content ? b.content.call(a[0]) : b.content)
637 | }, c.prototype.arrow = function () {
638 | return this.$arrow = this.$arrow || this.tip().find(".arrow")
639 | };
640 | var d = a.fn.popover;
641 | a.fn.popover = b, a.fn.popover.Constructor = c, a.fn.popover.noConflict = function () {
642 | return a.fn.popover = d, this
643 | }
644 | }(jQuery), +function (a) {
645 | "use strict";
646 | function b(c, d) {
647 | this.$body = a(document.body), this.$scrollElement = a(a(c).is(document.body) ? window : c), this.options = a.extend({}, b.DEFAULTS, d), this.selector = (this.options.target || "") + " .nav li > a", this.offsets = [], this.targets = [], this.activeTarget = null, this.scrollHeight = 0, this.$scrollElement.on("scroll.bs.scrollspy", a.proxy(this.process, this)), this.refresh(), this.process()
648 | }
649 |
650 | function c(c) {
651 | return this.each(function () {
652 | var d = a(this), e = d.data("bs.scrollspy"), f = "object" == typeof c && c;
653 | e || d.data("bs.scrollspy", e = new b(this, f)), "string" == typeof c && e[c]()
654 | })
655 | }
656 |
657 | b.VERSION = "3.3.5", b.DEFAULTS = {offset: 10}, b.prototype.getScrollHeight = function () {
658 | return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
659 | }, b.prototype.refresh = function () {
660 | var b = this, c = "offset", d = 0;
661 | this.offsets = [], this.targets = [], this.scrollHeight = this.getScrollHeight(), a.isWindow(this.$scrollElement[0]) || (c = "position", d = this.$scrollElement.scrollTop()), this.$body.find(this.selector).map(function () {
662 | var b = a(this), e = b.data("target") || b.attr("href"), f = /^#./.test(e) && a(e);
663 | return f && f.length && f.is(":visible") && [[f[c]().top + d, e]] || null
664 | }).sort(function (a, b) {
665 | return a[0] - b[0]
666 | }).each(function () {
667 | b.offsets.push(this[0]), b.targets.push(this[1])
668 | })
669 | }, b.prototype.process = function () {
670 | var a, b = this.$scrollElement.scrollTop() + this.options.offset, c = this.getScrollHeight(), d = this.options.offset + c - this.$scrollElement.height(), e = this.offsets, f = this.targets, g = this.activeTarget;
671 | if (this.scrollHeight != c && this.refresh(), b >= d)return g != (a = f[f.length - 1]) && this.activate(a);
672 | if (g && b < e[0])return this.activeTarget = null, this.clear();
673 | for (a = e.length; a--;)g != f[a] && b >= e[a] && (void 0 === e[a + 1] || b < e[a + 1]) && this.activate(f[a])
674 | }, b.prototype.activate = function (b) {
675 | this.activeTarget = b, this.clear();
676 | var c = this.selector + '[data-target="' + b + '"],' + this.selector + '[href="' + b + '"]', d = a(c).parents("li").addClass("active");
677 | d.parent(".dropdown-menu").length && (d = d.closest("li.dropdown").addClass("active")),
678 | d.trigger("activate.bs.scrollspy")
679 | }, b.prototype.clear = function () {
680 | a(this.selector).parentsUntil(this.options.target, ".active").removeClass("active")
681 | };
682 | var d = a.fn.scrollspy;
683 | a.fn.scrollspy = c, a.fn.scrollspy.Constructor = b, a.fn.scrollspy.noConflict = function () {
684 | return a.fn.scrollspy = d, this
685 | }, a(window).on("load.bs.scrollspy.data-api", function () {
686 | a('[data-spy="scroll"]').each(function () {
687 | var b = a(this);
688 | c.call(b, b.data())
689 | })
690 | })
691 | }(jQuery), +function (a) {
692 | "use strict";
693 | function b(b) {
694 | return this.each(function () {
695 | var d = a(this), e = d.data("bs.tab");
696 | e || d.data("bs.tab", e = new c(this)), "string" == typeof b && e[b]()
697 | })
698 | }
699 |
700 | var c = function (b) {
701 | this.element = a(b)
702 | };
703 | c.VERSION = "3.3.5", c.TRANSITION_DURATION = 150, c.prototype.show = function () {
704 | var b = this.element, c = b.closest("ul:not(.dropdown-menu)"), d = b.data("target");
705 | if (d || (d = b.attr("href"), d = d && d.replace(/.*(?=#[^\s]*$)/, "")), !b.parent("li").hasClass("active")) {
706 | var e = c.find(".active:last a"), f = a.Event("hide.bs.tab", {relatedTarget: b[0]}), g = a.Event("show.bs.tab", {relatedTarget: e[0]});
707 | if (e.trigger(f), b.trigger(g), !g.isDefaultPrevented() && !f.isDefaultPrevented()) {
708 | var h = a(d);
709 | this.activate(b.closest("li"), c), this.activate(h, h.parent(), function () {
710 | e.trigger({type: "hidden.bs.tab", relatedTarget: b[0]}), b.trigger({
711 | type: "shown.bs.tab",
712 | relatedTarget: e[0]
713 | })
714 | })
715 | }
716 | }
717 | }, c.prototype.activate = function (b, d, e) {
718 | function f() {
719 | g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded", !1), b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded", !0), h ? (b[0].offsetWidth, b.addClass("in")) : b.removeClass("fade"), b.parent(".dropdown-menu").length && b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded", !0), e && e()
720 | }
721 |
722 | var g = d.find("> .active"), h = e && a.support.transition && (g.length && g.hasClass("fade") || !!d.find("> .fade").length);
723 | g.length && h ? g.one("bsTransitionEnd", f).emulateTransitionEnd(c.TRANSITION_DURATION) : f(), g.removeClass("in")
724 | };
725 | var d = a.fn.tab;
726 | a.fn.tab = b, a.fn.tab.Constructor = c, a.fn.tab.noConflict = function () {
727 | return a.fn.tab = d, this
728 | };
729 | var e = function (c) {
730 | c.preventDefault(), b.call(a(this), "show")
731 | };
732 | a(document).on("click.bs.tab.data-api", '[data-toggle="tab"]', e).on("click.bs.tab.data-api", '[data-toggle="pill"]', e)
733 | }(jQuery), +function (a) {
734 | "use strict";
735 | function b(b) {
736 | return this.each(function () {
737 | var d = a(this), e = d.data("bs.affix"), f = "object" == typeof b && b;
738 | e || d.data("bs.affix", e = new c(this, f)), "string" == typeof b && e[b]()
739 | })
740 | }
741 |
742 | var c = function (b, d) {
743 | this.options = a.extend({}, c.DEFAULTS, d), this.$target = a(this.options.target).on("scroll.bs.affix.data-api", a.proxy(this.checkPosition, this)).on("click.bs.affix.data-api", a.proxy(this.checkPositionWithEventLoop, this)), this.$element = a(b), this.affixed = null, this.unpin = null, this.pinnedOffset = null, this.checkPosition()
744 | };
745 | c.VERSION = "3.3.5", c.RESET = "affix affix-top affix-bottom", c.DEFAULTS = {
746 | offset: 0,
747 | target: window
748 | }, c.prototype.getState = function (a, b, c, d) {
749 | var e = this.$target.scrollTop(), f = this.$element.offset(), g = this.$target.height();
750 | if (null != c && "top" == this.affixed)return c > e ? "top" : !1;
751 | if ("bottom" == this.affixed)return null != c ? e + this.unpin <= f.top ? !1 : "bottom" : a - d >= e + g ? !1 : "bottom";
752 | var h = null == this.affixed, i = h ? e : f.top, j = h ? g : b;
753 | return null != c && c >= e ? "top" : null != d && i + j >= a - d ? "bottom" : !1
754 | }, c.prototype.getPinnedOffset = function () {
755 | if (this.pinnedOffset)return this.pinnedOffset;
756 | this.$element.removeClass(c.RESET).addClass("affix");
757 | var a = this.$target.scrollTop(), b = this.$element.offset();
758 | return this.pinnedOffset = b.top - a
759 | }, c.prototype.checkPositionWithEventLoop = function () {
760 | setTimeout(a.proxy(this.checkPosition, this), 1)
761 | }, c.prototype.checkPosition = function () {
762 | if (this.$element.is(":visible")) {
763 | var b = this.$element.height(), d = this.options.offset, e = d.top, f = d.bottom, g = Math.max(a(document).height(), a(document.body).height());
764 | "object" != typeof d && (f = e = d), "function" == typeof e && (e = d.top(this.$element)), "function" == typeof f && (f = d.bottom(this.$element));
765 | var h = this.getState(g, b, e, f);
766 | if (this.affixed != h) {
767 | null != this.unpin && this.$element.css("top", "");
768 | var i = "affix" + (h ? "-" + h : ""), j = a.Event(i + ".bs.affix");
769 | if (this.$element.trigger(j), j.isDefaultPrevented())return;
770 | this.affixed = h, this.unpin = "bottom" == h ? this.getPinnedOffset() : null, this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix", "affixed") + ".bs.affix")
771 | }
772 | "bottom" == h && this.$element.offset({top: g - b - f})
773 | }
774 | };
775 | var d = a.fn.affix;
776 | a.fn.affix = b, a.fn.affix.Constructor = c, a.fn.affix.noConflict = function () {
777 | return a.fn.affix = d, this
778 | }, a(window).on("load", function () {
779 | a('[data-spy="affix"]').each(function () {
780 | var c = a(this), d = c.data();
781 | d.offset = d.offset || {}, null != d.offsetBottom && (d.offset.bottom = d.offsetBottom), null != d.offsetTop && (d.offset.top = d.offsetTop), b.call(c, d)
782 | })
783 | })
784 | }(jQuery);
--------------------------------------------------------------------------------