From 1aad210ae02fa2b647cfcc7590ed7616e16ad513 Mon Sep 17 00:00:00 2001 From: DOHA Date: Mon, 2 Mar 2015 21:33:00 +0200 Subject: [PATCH] add schedule post to reddit --- .../baeldung/config/PersistenceJPAConfig.java | 75 + .../baeldung/config/ServletInitializer.java | 2 +- .../java/org/baeldung/config/WebConfig.java | 34 +- .../persistence/dao/PostRepository.java | 15 + .../persistence/dao/UserRepository.java | 10 + .../org/baeldung/persistence/model/Post.java | 103 ++ .../org/baeldung/persistence/model/User.java | 104 ++ .../org/baeldung/web/RedditController.java | 102 +- .../baeldung/web/RestExceptionHandler.java | 4 +- .../baeldung/web/schedule/ScheduledTasks.java | 77 + .../src/main/resources/persistence.properties | 10 + .../main/webapp/WEB-INF/jsp/postListView.jsp | 54 + .../src/main/webapp/WEB-INF/jsp/reddit.jsp | 28 +- .../webapp/WEB-INF/jsp/schedulePostForm.jsp | 71 + .../webapp/WEB-INF/jsp/submissionForm.jsp | 24 + .../webapp/WEB-INF/jsp/submissionResponse.jsp | 25 +- .../main/webapp/resources/datetime-picker.css | 332 ++++ .../main/webapp/resources/datetime-picker.js | 1487 +++++++++++++++++ 18 files changed, 2513 insertions(+), 44 deletions(-) create mode 100644 spring-security-oauth/src/main/java/org/baeldung/config/PersistenceJPAConfig.java create mode 100644 spring-security-oauth/src/main/java/org/baeldung/persistence/dao/PostRepository.java create mode 100644 spring-security-oauth/src/main/java/org/baeldung/persistence/dao/UserRepository.java create mode 100644 spring-security-oauth/src/main/java/org/baeldung/persistence/model/Post.java create mode 100644 spring-security-oauth/src/main/java/org/baeldung/persistence/model/User.java create mode 100644 spring-security-oauth/src/main/java/org/baeldung/web/schedule/ScheduledTasks.java create mode 100644 spring-security-oauth/src/main/resources/persistence.properties create mode 100755 spring-security-oauth/src/main/webapp/WEB-INF/jsp/postListView.jsp create mode 100755 spring-security-oauth/src/main/webapp/WEB-INF/jsp/schedulePostForm.jsp create mode 100644 spring-security-oauth/src/main/webapp/resources/datetime-picker.css create mode 100644 spring-security-oauth/src/main/webapp/resources/datetime-picker.js diff --git a/spring-security-oauth/src/main/java/org/baeldung/config/PersistenceJPAConfig.java b/spring-security-oauth/src/main/java/org/baeldung/config/PersistenceJPAConfig.java new file mode 100644 index 0000000000..5675768a49 --- /dev/null +++ b/spring-security-oauth/src/main/java/org/baeldung/config/PersistenceJPAConfig.java @@ -0,0 +1,75 @@ +package org.baeldung.config; + +import java.util.Properties; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableTransactionManagement +@PropertySource({ "classpath:persistence.properties" }) +@ComponentScan({ "org.baeldung.persistence" }) +@EnableJpaRepositories(basePackages = "org.baeldung.persistence.dao") +public class PersistenceJPAConfig { + + @Autowired + private Environment env; + + public PersistenceJPAConfig() { + super(); + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(dataSource()); + em.setPackagesToScan(new String[] { "org.baeldung.persistence.model" }); + final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); + em.setJpaVendorAdapter(vendorAdapter); + em.setJpaProperties(additionalProperties()); + return em; + } + + @Bean + public DataSource dataSource() { + final DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName")); + dataSource.setUrl(env.getProperty("jdbc.url")); + dataSource.setUsername(env.getProperty("jdbc.user")); + dataSource.setPassword(env.getProperty("jdbc.pass")); + return dataSource; + } + + @Bean + public JpaTransactionManager transactionManager() { + final JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); + return transactionManager; + } + + @Bean + public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { + return new PersistenceExceptionTranslationPostProcessor(); + } + + final Properties additionalProperties() { + final Properties hibernateProperties = new Properties(); + hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto")); + hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect")); + return hibernateProperties; + } + +} diff --git a/spring-security-oauth/src/main/java/org/baeldung/config/ServletInitializer.java b/spring-security-oauth/src/main/java/org/baeldung/config/ServletInitializer.java index d9d3821757..96f1ab48a1 100644 --- a/spring-security-oauth/src/main/java/org/baeldung/config/ServletInitializer.java +++ b/spring-security-oauth/src/main/java/org/baeldung/config/ServletInitializer.java @@ -13,7 +13,7 @@ public class ServletInitializer extends AbstractDispatcherServletInitializer { @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); - context.register(WebConfig.class); + context.register(PersistenceJPAConfig.class, WebConfig.class); return context; } diff --git a/spring-security-oauth/src/main/java/org/baeldung/config/WebConfig.java b/spring-security-oauth/src/main/java/org/baeldung/config/WebConfig.java index fe987b8c97..028645f8fc 100644 --- a/spring-security-oauth/src/main/java/org/baeldung/config/WebConfig.java +++ b/spring-security-oauth/src/main/java/org/baeldung/config/WebConfig.java @@ -2,13 +2,15 @@ package org.baeldung.config; import java.util.Arrays; -import org.baeldung.web.RedditController; -import org.baeldung.web.RestExceptionHandler; +import org.baeldung.web.schedule.ScheduledTasks; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.security.oauth2.client.OAuth2ClientContext; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; @@ -28,6 +30,9 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @EnableWebMvc +@EnableScheduling +@EnableAsync +@ComponentScan({ "org.baeldung.web" }) public class WebConfig extends WebMvcConfigurerAdapter { @Bean @@ -48,16 +53,23 @@ public class WebConfig extends WebMvcConfigurerAdapter { configurer.enable(); } + // @Bean + // public RedditController redditController(OAuth2RestTemplate redditRestTemplate) { + // RedditController controller = new RedditController(); + // controller.setRedditRestTemplate(redditRestTemplate); + // return controller; + // } + // + // @Bean + // public RestExceptionHandler restExceptionHandler() { + // return new RestExceptionHandler(); + // } + // @Bean - public RedditController redditController(OAuth2RestTemplate redditRestTemplate) { - RedditController controller = new RedditController(); - controller.setRedditRestTemplate(redditRestTemplate); - return controller; - } - - @Bean - public RestExceptionHandler restExceptionHandler() { - return new RestExceptionHandler(); + public ScheduledTasks scheduledTasks(OAuth2ProtectedResourceDetails reddit) { + ScheduledTasks s = new ScheduledTasks(); + s.setRedditRestTemplate(new OAuth2RestTemplate(reddit)); + return s; } public void addResourceHandlers(ResourceHandlerRegistry registry) { diff --git a/spring-security-oauth/src/main/java/org/baeldung/persistence/dao/PostRepository.java b/spring-security-oauth/src/main/java/org/baeldung/persistence/dao/PostRepository.java new file mode 100644 index 0000000000..13d393bc4b --- /dev/null +++ b/spring-security-oauth/src/main/java/org/baeldung/persistence/dao/PostRepository.java @@ -0,0 +1,15 @@ +package org.baeldung.persistence.dao; + +import java.util.Date; +import java.util.List; + +import org.baeldung.persistence.model.Post; +import org.baeldung.persistence.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostRepository extends JpaRepository { + + public List findBySubmissionDateBefore(Date date); + + public List findByUser(User user); +} \ No newline at end of file diff --git a/spring-security-oauth/src/main/java/org/baeldung/persistence/dao/UserRepository.java b/spring-security-oauth/src/main/java/org/baeldung/persistence/dao/UserRepository.java new file mode 100644 index 0000000000..bfbd4d5cf0 --- /dev/null +++ b/spring-security-oauth/src/main/java/org/baeldung/persistence/dao/UserRepository.java @@ -0,0 +1,10 @@ +package org.baeldung.persistence.dao; + +import org.baeldung.persistence.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + public User findByUsername(String username); + + public User findByAccessToken(String token); +} \ No newline at end of file diff --git a/spring-security-oauth/src/main/java/org/baeldung/persistence/model/Post.java b/spring-security-oauth/src/main/java/org/baeldung/persistence/model/Post.java new file mode 100644 index 0000000000..3f0f7cac5f --- /dev/null +++ b/spring-security-oauth/src/main/java/org/baeldung/persistence/model/Post.java @@ -0,0 +1,103 @@ +package org.baeldung.persistence.model; + +import java.util.Date; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +@Entity +public class Post { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String title; + + private String subreddit; + + private String url; + + private Date submissionDate; + + private boolean isSent; + + private String submissionResponse; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + public Post() { + super(); + } + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSubreddit() { + return subreddit; + } + + public void setSubreddit(String subreddit) { + this.subreddit = subreddit; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Date getSubmissionDate() { + return submissionDate; + } + + public void setSubmissionDate(Date submissionDate) { + this.submissionDate = submissionDate; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public boolean isSent() { + return isSent; + } + + public void setSent(boolean isSent) { + this.isSent = isSent; + } + + public String getSubmissionResponse() { + return submissionResponse; + } + + public void setSubmissionResponse(String submissionResponse) { + this.submissionResponse = submissionResponse; + } + +} \ No newline at end of file diff --git a/spring-security-oauth/src/main/java/org/baeldung/persistence/model/User.java b/spring-security-oauth/src/main/java/org/baeldung/persistence/model/User.java new file mode 100644 index 0000000000..a854c33807 --- /dev/null +++ b/spring-security-oauth/src/main/java/org/baeldung/persistence/model/User.java @@ -0,0 +1,104 @@ +package org.baeldung.persistence.model; + +import java.util.Date; +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String username; + + private String accessToken; + + private String refreshToken; + + private Date tokenExpiration; + + @OneToMany(mappedBy = "user") + private List posts; + + public User() { + super(); + } + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public Date getTokenExpiration() { + return tokenExpiration; + } + + public void setTokenExpiration(Date tokenExpiration) { + this.tokenExpiration = tokenExpiration; + } + + public List getPosts() { + return posts; + } + + public void setPosts(List posts) { + this.posts = posts; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final User user = (User) obj; + if (!username.equals(user.username)) + return false; + return true; + } + +} \ No newline at end of file diff --git a/spring-security-oauth/src/main/java/org/baeldung/web/RedditController.java b/spring-security-oauth/src/main/java/org/baeldung/web/RedditController.java index 869553448b..4a743b1b92 100644 --- a/spring-security-oauth/src/main/java/org/baeldung/web/RedditController.java +++ b/spring-security-oauth/src/main/java/org/baeldung/web/RedditController.java @@ -1,14 +1,21 @@ package org.baeldung.web; -import java.io.IOException; -import java.util.ArrayList; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.baeldung.persistence.dao.PostRepository; +import org.baeldung.persistence.dao.UserRepository; +import org.baeldung.persistence.model.Post; +import org.baeldung.persistence.model.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.LinkedMultiValueMap; @@ -16,26 +23,35 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; @Controller public class RedditController { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + @Autowired private OAuth2RestTemplate redditRestTemplate; - private final Logger LOGGER = LoggerFactory.getLogger(getClass()); + @Autowired + private UserRepository userReopsitory; + + @Autowired + private PostRepository postReopsitory; @RequestMapping("/info") - public String getInfo(Model model) { + public final String getInfo(Model model) { JsonNode node = redditRestTemplate.getForObject("https://oauth.reddit.com/api/v1/me", JsonNode.class); String name = node.get("name").asText(); + addUser(name, redditRestTemplate.getAccessToken()); model.addAttribute("info", name); return "reddit"; } @RequestMapping("/submit") - public String submit(Model model, @RequestParam Map formParams) { + public final String submit(Model model, @RequestParam Map formParams) { MultiValueMap param = new LinkedMultiValueMap(); param.add("api_type", "json"); param.add("kind", "link"); @@ -47,16 +63,16 @@ public class RedditController { param.add(entry.getKey(), entry.getValue()); } - LOGGER.info("User submitting Link with these parameters: " + formParams.entrySet()); + logger.info("User submitting Link with these parameters: " + formParams.entrySet()); JsonNode node = redditRestTemplate.postForObject("https://oauth.reddit.com/api/submit", param, JsonNode.class); - LOGGER.info("Full Reddit Response: " + node.toString()); + logger.info("Full Reddit Response: " + node.toString()); String responseMsg = parseResponse(node); model.addAttribute("msg", responseMsg); return "submissionResponse"; } @RequestMapping("/post") - public String showSubmissionForm(Model model) { + public final String showSubmissionForm(Model model) { String needsCaptchaResult = needsCaptcha(); if (needsCaptchaResult.equalsIgnoreCase("true")) { String iden = getNewCaptcha(); @@ -65,25 +81,53 @@ public class RedditController { return "submissionForm"; } - // === private - - public List getSubreddit() throws JsonProcessingException, IOException { - String result = redditRestTemplate.getForObject("https://oauth.reddit.com/subreddits/popular?limit=50", String.class); - JsonNode node = new ObjectMapper().readTree(result); - node = node.get("data").get("children"); - List subreddits = new ArrayList(); - for (JsonNode child : node) { - subreddits.add(child.get("data").get("display_name").asText()); + @RequestMapping("/postSchedule") + public final String showSchedulePostForm(Model model) { + String needsCaptchaResult = needsCaptcha(); + if (needsCaptchaResult.equalsIgnoreCase("true")) { + model.addAttribute("msg", "Sorry, You do not have enought karma"); + return "submissionResponse"; } - return subreddits; + return "schedulePostForm"; } - private String needsCaptcha() { + @RequestMapping("/schedule") + public final String schedule(Model model, @RequestParam Map formParams) throws ParseException { + logger.info("User scheduling Post with these parameters: " + formParams.entrySet()); + User user = userReopsitory.findByAccessToken(redditRestTemplate.getAccessToken().getValue()); + Post post = new Post(); + post.setUser(user); + post.setSent(false); + post.setTitle(formParams.get("title")); + post.setSubreddit(formParams.get("sr")); + post.setUrl(formParams.get("url")); + post.setSubmissionDate(dateFormat.parse(formParams.get("date"))); + if (post.getSubmissionDate().before(new Date())) { + model.addAttribute("msg", "Invalid date"); + return "submissionResponse"; + } + postReopsitory.save(post); + List posts = postReopsitory.findByUser(user); + model.addAttribute("posts", posts); + return "postListView"; + } + + @RequestMapping("/posts") + public final String getScheduledPosts(Model model) { + User user = userReopsitory.findByAccessToken(redditRestTemplate.getAccessToken().getValue()); + List posts = postReopsitory.findByUser(user); + model.addAttribute("posts", posts); + return "postListView"; + } + + // === private + + private final String needsCaptcha() { String result = redditRestTemplate.getForObject("https://oauth.reddit.com/api/needs_captcha.json", String.class); return result; } - private String getNewCaptcha() { + private final String getNewCaptcha() { Map param = new HashMap(); param.put("api_type", "json"); @@ -92,7 +136,7 @@ public class RedditController { return split[split.length - 2]; } - private String parseResponse(JsonNode node) { + private final String parseResponse(JsonNode node) { String result = ""; JsonNode errorNode = node.get("json").get("errors").get(0); if (errorNode != null) { @@ -108,8 +152,16 @@ public class RedditController { } } - public void setRedditRestTemplate(OAuth2RestTemplate redditRestTemplate) { - this.redditRestTemplate = redditRestTemplate; + private final void addUser(String name, OAuth2AccessToken token) { + User user = userReopsitory.findByUsername(name); + if (user == null) { + user = new User(); + user.setUsername(name); + user.setAccessToken(token.getValue()); + user.setRefreshToken(token.getRefreshToken().getValue()); + user.setTokenExpiration(token.getExpiration()); + userReopsitory.save(user); + } } } diff --git a/spring-security-oauth/src/main/java/org/baeldung/web/RestExceptionHandler.java b/spring-security-oauth/src/main/java/org/baeldung/web/RestExceptionHandler.java index 0a1c671a0b..61ee78be43 100644 --- a/spring-security-oauth/src/main/java/org/baeldung/web/RestExceptionHandler.java +++ b/spring-security-oauth/src/main/java/org/baeldung/web/RestExceptionHandler.java @@ -14,9 +14,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep @ControllerAdvice public class RestExceptionHandler extends ResponseEntityExceptionHandler implements Serializable { - /** - * - */ + private static final long serialVersionUID = -3861125729653781371L; public RestExceptionHandler() { diff --git a/spring-security-oauth/src/main/java/org/baeldung/web/schedule/ScheduledTasks.java b/spring-security-oauth/src/main/java/org/baeldung/web/schedule/ScheduledTasks.java new file mode 100644 index 0000000000..fec528c042 --- /dev/null +++ b/spring-security-oauth/src/main/java/org/baeldung/web/schedule/ScheduledTasks.java @@ -0,0 +1,77 @@ +package org.baeldung.web.schedule; + +import java.io.IOException; +import java.text.ParseException; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import org.baeldung.persistence.dao.PostRepository; +import org.baeldung.persistence.model.Post; +import org.baeldung.persistence.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; + +@EnableScheduling +public class ScheduledTasks { + + private OAuth2RestTemplate redditRestTemplate; + + @Autowired + private PostRepository postReopsitory; + + @Scheduled(fixedRate = 5 * 60 * 1000) + public void reportCurrentTime() throws JsonProcessingException, IOException, ParseException { + List posts = postReopsitory.findBySubmissionDateBefore(new Date()); + System.out.println(posts.size()); + for (Post post : posts) { + if (post.isSent()) + continue; + User user = post.getUser(); + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(user.getAccessToken()); + token.setRefreshToken(new DefaultOAuth2RefreshToken((user.getRefreshToken()))); + token.setExpiration(user.getTokenExpiration()); + redditRestTemplate.getOAuth2ClientContext().setAccessToken(token); + // + UsernamePasswordAuthenticationToken userAuthToken = new UsernamePasswordAuthenticationToken(user.getUsername(), token.getValue(), Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))); + SecurityContextHolder.getContext().setAuthentication(userAuthToken); + // + + MultiValueMap param = new LinkedMultiValueMap(); + param.add("api_type", "json"); + param.add("kind", "link"); + param.add("resubmit", "true"); + param.add("sendreplies", "false"); + param.add("then", "comments"); + param.add("title", post.getTitle()); + param.add("sr", post.getSubreddit()); + param.add("url", post.getUrl()); + + JsonNode node = redditRestTemplate.postForObject("https://oauth.reddit.com/api/submit", param, JsonNode.class); + JsonNode errorNode = node.get("json").get("errors").get(0); + if (errorNode == null) { + post.setSent(true); + postReopsitory.save(post); + } else { + post.setSubmissionResponse(errorNode.toString()); + postReopsitory.save(post); + } + } + } + + public void setRedditRestTemplate(OAuth2RestTemplate redditRestTemplate) { + this.redditRestTemplate = redditRestTemplate; + } +} \ No newline at end of file diff --git a/spring-security-oauth/src/main/resources/persistence.properties b/spring-security-oauth/src/main/resources/persistence.properties new file mode 100644 index 0000000000..0bfb48fce4 --- /dev/null +++ b/spring-security-oauth/src/main/resources/persistence.properties @@ -0,0 +1,10 @@ +################### DataSource Configuration ########################## +jdbc.driverClassName=com.mysql.jdbc.Driver +jdbc.url=jdbc:mysql://localhost:3306/oauth_reddit?createDatabaseIfNotExist=true +jdbc.user=tutorialuser +jdbc.pass=tutorialmy5ql +init-db=false +################### Hibernate Configuration ########################## +hibernate.dialect=org.hibernate.dialect.MySQLDialect +hibernate.show_sql=false +hibernate.hbm2ddl.auto=create-drop diff --git a/spring-security-oauth/src/main/webapp/WEB-INF/jsp/postListView.jsp b/spring-security-oauth/src/main/webapp/WEB-INF/jsp/postListView.jsp new file mode 100755 index 0000000000..fe2e19ec4a --- /dev/null +++ b/spring-security-oauth/src/main/webapp/WEB-INF/jsp/postListView.jsp @@ -0,0 +1,54 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> + + + +Spring Security OAuth + + + + + +
+

My Scheduled Posts

+ + + + + + + + + + class="success"> + + + + + +
Post titleSubmission DateNotes
+
+ + \ No newline at end of file diff --git a/spring-security-oauth/src/main/webapp/WEB-INF/jsp/reddit.jsp b/spring-security-oauth/src/main/webapp/WEB-INF/jsp/reddit.jsp index dd2e0c486a..0c561fb675 100755 --- a/spring-security-oauth/src/main/webapp/WEB-INF/jsp/reddit.jsp +++ b/spring-security-oauth/src/main/webapp/WEB-INF/jsp/reddit.jsp @@ -7,12 +7,34 @@ +
-

Your Reddit Info

- Your reddit username is ${info} -


+

Welcome, ${info}

Submit to Reddit
diff --git a/spring-security-oauth/src/main/webapp/WEB-INF/jsp/schedulePostForm.jsp b/spring-security-oauth/src/main/webapp/WEB-INF/jsp/schedulePostForm.jsp new file mode 100755 index 0000000000..c07777a6b3 --- /dev/null +++ b/spring-security-oauth/src/main/webapp/WEB-INF/jsp/schedulePostForm.jsp @@ -0,0 +1,71 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> + + + +Spring Security OAuth + +"> + + + + + + +
+

Schedule Post

+
+
+
+ + +
+

+
+ + +
+

+
+ + +
+

+ + + + + +

+ +
+
+
+ + \ No newline at end of file diff --git a/spring-security-oauth/src/main/webapp/WEB-INF/jsp/submissionForm.jsp b/spring-security-oauth/src/main/webapp/WEB-INF/jsp/submissionForm.jsp index 6175d1e517..88caada900 100755 --- a/spring-security-oauth/src/main/webapp/WEB-INF/jsp/submissionForm.jsp +++ b/spring-security-oauth/src/main/webapp/WEB-INF/jsp/submissionForm.jsp @@ -7,6 +7,30 @@ +

Submit to Reddit

diff --git a/spring-security-oauth/src/main/webapp/WEB-INF/jsp/submissionResponse.jsp b/spring-security-oauth/src/main/webapp/WEB-INF/jsp/submissionResponse.jsp index ef7243984f..ad5b7c5070 100755 --- a/spring-security-oauth/src/main/webapp/WEB-INF/jsp/submissionResponse.jsp +++ b/spring-security-oauth/src/main/webapp/WEB-INF/jsp/submissionResponse.jsp @@ -7,9 +7,32 @@ + \ No newline at end of file diff --git a/spring-security-oauth/src/main/webapp/resources/datetime-picker.css b/spring-security-oauth/src/main/webapp/resources/datetime-picker.css new file mode 100644 index 0000000000..89e3b67eb6 --- /dev/null +++ b/spring-security-oauth/src/main/webapp/resources/datetime-picker.css @@ -0,0 +1,332 @@ +/** + * Style-sheet for dtpicker + * https://github.com/mugifly/jquery-simple-datetimepicker + */ + +.datepicker { + position: relative; + display: inline-block; + font: 15px/1.5 "Helvetica Neue", mplus-2c, Helvetica, Arial, "Hiragino Kaku Gothic Pro", Meiryo, sans-serif; + font-weight: 300; + border: 1px solid #dfdfdf; + border-radius: 3px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + box-shadow: 0.5px 0.5px 0px #c8c8c8; + -webkit-box-shadow: 0.5px 0.5px 3px #eeeeee; + -moz-box-shadow: 0.5px 0.5px 3px #eeeeee; +} + +/* + * datepicker_header +*/ + +.datepicker > .datepicker_header{ + padding-top: 2px; + padding-bottom: 2px; + padding-left: 5px; + padding-right: 5px; + background-color: #eeeeee; + color: #3f3f3f; + text-align: center; + font-size: 9pt; + font-weight: bold; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; +} + +.datepicker > .datepicker_header > a { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + cursor: pointer; + color: #3b7796; + padding: 3px 16px; + font-size: 20px; +} + +.datepicker > .datepicker_header > a:hover { + color: #303030; + background-color: #c8c8c8; +} + +.datepicker > .datepicker_header > a:active { + color: #ffffff; + background-color: #808080; +} + +.datepicker > .datepicker_header > span { + margin-left: 20px; + margin-right: 20px; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; +} + +.datepicker > .datepicker_header > .icon-home { + position: absolute; + display: block; + width: 16px; + height: 16px; + vertical-align: middle; + padding: 8px; + top: 0; + left: 0; +} + +.datepicker > .datepicker_header > .icon-close { + position: absolute; + display: block; + width: 16px; + height: 16px; + vertical-align: middle; + padding: 8px; + top: 0; + right: 0; +} +.datepicker > .datepicker_header > .icon-home > div { + width: 16px; + height: 16px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAQCAYAAAB3AH1ZAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAepJREFUSEudk71Kw2AUhnVxVnCyN1EcCjXUujmKF1AoRRCcNU4OIg5dtXF36tAb6B107qjg0iC4STt0EdTnTU8+v5rGNh54aM7Pe/J+abLmR3AZ7QeXD0+WZkI9zViaiXq9vg+5evU0Y+ksgjCqsrgPA2jBOzQs91FNPc0o70vLQtGHAbTgHRqW+6imnmaUS1PVqWL4SgijJr8juHK1H1Qb2Uxai1kivowmjODKq6Wopp5m0losA95Noh43ONXi+XpCPOsx49W9ZaIHp+CbSlFNPc24+i8DxfGX/YeZgTDqQrsgXc9AF9oFkcY9gQN7J3MjuOhs2WUS0ngGluqZmdNLs7IBTltmZsrvsZUKGaBfhik4vTSw3EBwfrdBfwhvwcX9tpVXNkBvA4bwBk4vDSw2UD27WbdLnf42mQmjIysl8ZeBWq3m9PRubWZOL43qGQOccpf8lc9tj5tWuP7g+tHaLqSR1pY7Pde78Ap7UIEPyOip5RgIOzvkzzCGF4gxtWltF9LAIgM78AxjeAF9/xk9tfy/gBuWOLVMfPIUDq08F3kGFOQlkIlPWKiXBvJfQl6+EjdvWJqJvwwoqMlErl4aabVowmmvbWEBEs2EJeLaFhZBmone8hMtAj2JIkykZYmQifRprAqa+sk3UgBXF8VWaxMAAAAASUVORK5CYII=); +} +.datepicker > .datepicker_header > .icon-close> div { + width: 16px; + height: 16px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAQCAYAAAB3AH1ZAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAfhJREFUSEvNVEtKA0EUHHBhsvZ7D0HF4PRMnERxI+hCQ4xuBE/g5yLRA+il/CCIXkDElRGt6qkZO91j1JUW1KJf1XvVn2Gif4XGUX986aRf0zIANXq0DBBv7Iwvb+192U+NHi2H0Tg+O2ycnN2DV+CcyiVQW5B2T6/KJUxr7dAkySN4bVbaiyqXMM1s3mr0wKtyDgytgQ/gu/gKbkumvgsOpJH0lieNN3s1O9iYd/EN657kyJikZ2uFDi97JDOgX8fAWyeg2MQO6IeT8Pbrao+Wtw/qJklvywBnE5ZuOAkve9SeA0PnQT+Ia7/2BoZXnK0uBkH5Oqxl4RNZYHAH9ANdMnxP9gA42X5FoMuhp6kEAnjlDPpVeIERm0B4+m0/N9AFq26Bta5sX0JvPvDCycFPTs+PbtQTUOvIHgAhu+DoJ4BH9mFgcAzyy3cDee3+c9ATq60ErjfDcP/kDPQ3hJtIM7XlwMAJ8FoBBe2bi/4m6J1QO34yrWlc740XxDffr/wm6EWP2u0GJsEnDS/DJVPvge7T0DspmT+aGQx+cUKGPriKTcCbzEhGwOn5GAZegM/gHdiSVAK1VBo9F+yRFJn2+hgGXtrBSXpn0mbQb5orbavl4ZfskZRDm5gGp1QKQM16nPACdhNpczZur3+ezAM1eoLwv0MUfQBkquZISGUUwgAAAABJRU5ErkJggg==); +} +.datepicker > .datepicker_header > a:hover > div, .datepicker > .datepicker_header > a:hover > div { + background-position: -16px 0px; +} + +/* + * datepicker_inner_container +*/ + +.datepicker > .datepicker_inner_container { + margin: -2px 0px -2px 0px; + background-color: #d2d2d2; + border: 1px solid #c8c8c8; + border-radius: 3px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + + box-shadow: 0.5px 0px 3px #c8c8c8; + -webkit-box-shadow: 0.5px 0px 3px #c8c8c8; + -moz-box-shadow: 0.5px 0px 3px #c8c8c8; +} + +.datepicker > .datepicker_inner_container:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; +} + +/* + * datepicker_inner_container > datepicker_calendar +*/ + +.datepicker > .datepicker_inner_container > .datepicker_calendar { + float: left; + width: 18.3em; + + margin-top: -0.5px; + margin-left: -1px; + margin-bottom: -2px; + + background-color: #ffffff; + border: 1px solid #c8c8c8; + + border-top:none; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + -webkit-border-top-left-radius: 3px; + -webkit-border-bottom-left-radius: 3px; + -moz-border-radius-topleft: 3px; + -moz-border-radius-bottomleft: 3px; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > table { + padding: 10px; +} + +/* + * datepicker_inner_container > datepicker_calendar > datepicker_table > tbody > tr > th (WDay-cell) +*/ + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > th { + color: #646464; + width: 18px; + font-size: small; + font-weight: normal; + text-align:center; +} + +/* + * datepicker_inner_container > datepicker_calendar > datepicker_table > tbody > tr > td (Day-cell) +*/ + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td { + color: #000000; + font-size: small; + text-align:center; + + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + cursor: pointer; + padding: 10px; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.today { + border-bottom: #bfbfbf solid 2px; + margin-bottom: -2px; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.wday_sat { + color: #0044aa; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.wday_sun { + color: #e13b00; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.day_another_month { + color: #cccccc; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.day_in_past { + cursor: default; + color: #cccccc; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.day_in_unallowed { + cursor: default; + color: #cccccc; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.out_of_range { + cursor: default; + color: #cccccc; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.active { + color: #ffffff; + background-color: #808080; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.hover { + color: #000000; + background-color: #c8c8c8; +} + +/* + * datepicker_inner_container > datepicker_timelist +*/ + +.datepicker > .datepicker_inner_container > .datepicker_timelist { + float: left; + + margin-top: -0.5px; + padding: 5px 0px; + + overflow: auto; + overflow-x: hidden; + + background-color: #ffffff; + + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + -webkit-border-top-right-radius: 3px; + -webkit-border-bottom-right-radius: 3px; + -moz-border-radius-topright: 3px; + -moz-border-radius-bottomright: 3px; + text-align: right; + width: 4.9em; +} + +/* +.datepicker > .datepicker_inner_container > .datepicker_timelist::after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; +} +*/ + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar { + overflow: hidden; + width: 6px; + background: #fafafa; + + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + -webkit-border-top-right-radius: 3px; + -webkit-border-bottom-right-radius: 3px; + -moz-border-radius-topright: 3px; + -moz-border-radius-bottomright: 3px; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar:horizontal { + height: 1px; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-button { + display: none; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-piece { + background: #eee; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-piece:start { + background: #eee; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-thumb { + background: #aaaaaa; + border-radius: 3px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-corner { + background: #333; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item { + padding-top: 5px; + padding-bottom:5px; + padding-left: 7px; + padding-right: 7px; + margin-top: 5px; + margin-bottom: 2px; + font-size: small; + + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + cursor: pointer; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item.time_in_past { + cursor: default; + color: #cccccc; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item.out_of_range { + cursor: default; + color: #cccccc; +} +.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item.active { + color: #ffffff; + background-color: #808080; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item.hover { + color: #000000; + background-color: #c8c8c8; +} \ No newline at end of file diff --git a/spring-security-oauth/src/main/webapp/resources/datetime-picker.js b/spring-security-oauth/src/main/webapp/resources/datetime-picker.js new file mode 100644 index 0000000000..832a70a06e --- /dev/null +++ b/spring-security-oauth/src/main/webapp/resources/datetime-picker.js @@ -0,0 +1,1487 @@ +/** + * jquery-simple-datetimepicker (jquery.simple-dtpicker.js) + * v1.13.0 + * (c) Masanori Ohgita - 2014. + * https://github.com/mugifly/jquery-simple-datetimepicker + */ + + (function($) { + var lang = { + en: { + days: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], + months: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], + sep: '-', + format: 'YYYY-MM-DD hh:mm', + prevMonth: 'Previous month', + nextMonth: 'Next month', + today: 'Today' + }, + ro:{ + days: ['Dum', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sâm'], + months: ['Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Iul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + sep: '.', + format: 'DD.MM.YYYY hh:mm', + prevMonth: 'Luna precedentă', + nextMonth: 'Luna următoare', + today: 'Azi' + }, + + ja: { + days: ['日', '月', '火', '水', '木', '金', '土'], + months: [ "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12" ], + sep: '/', + format: 'YYYY/MM/DD hh:mm' + }, + ru: { + days: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'], + months: [ "Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек" ], + format: 'DD.MM.YYYY hh:mm' + }, + br: { + days: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'], + months: [ "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro" ], + format: 'DD/MM/YYYY hh:mm' + }, + pt: { + days: ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'], + months: [ "janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro" ] + }, + cn: { + days: ['日', '一', '二', '三', '四', '五', '六'], + months: [ "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月" ] + }, + da: { + days: ['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø'], + months: [ "Jan", "Feb", "Mar", "Apr", "Maj", "Juni", "Juli", "Aug", "Sept", "Okt", "Nov", "Dec" ], + sep: '-', + format: 'DD-MM-YYYY hh:mm', + prevMonth: 'Forrige måned', + nextMonth: 'Næste måned', + today: 'I dag' + }, + de: { + days: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], + months: [ "Jan", "Feb", "März", "Apr", "Mai", "Juni", "Juli", "Aug", "Sept", "Okt", "Nov", "Dez" ], + format: 'DD.MM.YYYY hh:mm' + }, + sv: { + days: ['Sö', 'Må', 'Ti', 'On', 'To', 'Fr', 'Lö'], + months: [ "Jan", "Feb", "Mar", "Apr", "Maj", "Juni", "Juli", "Aug", "Sept", "Okt", "Nov", "Dec" ] + }, + id: { + days: ['Min','Sen','Sel', 'Rab', 'Kam', 'Jum', 'Sab'], + months: [ "Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des" ] + }, + it: { + days: ['Dom','Lun','Mar', 'Mer', 'Gio', 'Ven', 'Sab'], + months: [ "Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic" ], + format: 'DD/MM/YYYY hh:mm' + }, + tr: { + days: ['Pz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cu', 'Cts'], + months: [ "Ock", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Agu", "Eyl", "Ekm", "Kas", "Arlk" ] + }, + es: { + days: ['dom', 'lun', 'mar', 'miér', 'jue', 'vie', 'sáb'], + months: [ "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic" ], + format: 'DD/MM/YYYY hh:mm' + }, + ko: { + days: ['일', '월', '화', '수', '목', '금', '토'], + months: [ "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12" ], + sep: '/', + prevMonth: '이전 달', + nextMonth: '다음 달', + today: '오늘' + }, + nl: { + days: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + months: [ "jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec" ], + format: 'DD-MM-YYYY hh:mm' + }, + cz: { + days: ['Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So'], + months: [ "Led", "Úno", "Bře", "Dub", "Kvě", "Čer", "Čvc", "Srp", "Zář", "Říj", "Lis", "Pro" ], + format: 'DD.MM.YYYY hh:mm' + }, + fr: { + days: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'], + months: [ "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre" ], + format: 'DD-MM-YYYY hh:mm' + }, + pl: { + days: ['N', 'Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So'], + months: [ "Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień" ], + sep: '-', + format: 'YYYY-MM-DD hh:mm', + prevMonth: 'Poprzedni miesiąc', + nextMonth: 'Następny miesiąc', + today: 'Dzisiaj' + }, + gr: { + days: ['Κυ', 'Δε', 'Τρ', 'Τε', 'Πε', 'Πα', 'Σα'], + months: [ "Ιαν", "Φεβ", "Μαρ", "Απρ", "Μαϊ", "Ιουν", "Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ" ], + sep: '-', + format: 'DD-MM-YYYY hh:mm', + prevMonth: 'Προηγ. μήνας', + nextMonth: 'Επόμ. μήνας', + today: 'Σήμερα' + }, + ua: { + days: ["Неділя","Понеділок","Вівторок","Cереда","Четвер","П'ятниця","Субота"], + months: ["Cічень","Лютий","Березень","Квітень","Травень","Червня","Липня","Серпня","Вересня","Жовтень","Листопада","Грудня"], + format: 'YYYY-MM-DD hh:mm', + prevMonth: 'Попередній місяць', + nextMonth: 'Наступний місяць', + today: 'Cьогодні' + }, + et: { + days: ['P', 'E', 'T', 'K', 'N', 'R', 'L'], + months: [ "Jaan", "Veebr", "Märts", "Apr", "Mai", "Juun", "Juul", "Aug", "Sept", "Okt", "Nov", "Dets" ], + sep: '.', + format: 'DD.MM.YYYY hh:mm', + prevMonth: 'Eelmine kuu', + nextMonth: 'Järgmine kuu', + today: 'Täna' + }, + hu: { + days: ['Va', 'Hé', 'Ke', 'Sze', 'Cs', 'Pé', 'Szo'], + months: [ "Jan", "Feb", "Már", "Ápr", "Máj", "Jún", "Júl", "Aug", "Szep", "Okt", "Nov", "Dec" ], + sep: '-', + format: 'YYYY-MM-DD hh:mm:00', + prevMonth: 'Előző hónap', + nextMonth: 'Következő hónap', + today: 'Ma' + } + }; + /* ----- */ + + /** + PickerHandler Object + **/ + var PickerHandler = function($picker, $input){ + this.$pickerObject = $picker; + this.$inputObject = $input; + }; + + /* Get a picker */ + PickerHandler.prototype.getPicker = function(){ + return this.$pickerObject; + }; + + /* Get a input-field */ + PickerHandler.prototype.getInput = function(){ + return this.$inputObject; + }; + + /* Get the display state of a picker */ + PickerHandler.prototype.isShow = function(){ + var is_show = true; + if (this.$pickerObject.css('display') == 'none') { + is_show = false; + } + return is_show; + }; + + /* Show a picker */ + PickerHandler.prototype.show = function(){ + var $picker = this.$pickerObject; + var $input = this.$inputObject; + + $picker.show(); + + ActivePickerId = $input.data('pickerId'); + + if ($input != null && $picker.data('isInline') === false) { // Float mode + // Relocate a picker to position of the appended input-field + this._relocate(); + } + }; + + /* Hide a picker */ + PickerHandler.prototype.hide = function(){ + var $picker = this.$pickerObject; + var $input = this.$inputObject; + $picker.hide(); + }; + + /* Get a selected date from a picker */ + PickerHandler.prototype.getDate = function(){ + var $picker = this.$pickerObject; + var $input = this.$inputObject; + return getPickedDate($picker); + }; + + /* Set a specific date to a picker */ + PickerHandler.prototype.setDate = function(date){ + var $picker = this.$pickerObject; + var $input = this.$inputObject; + if (!isObj('Date', date)) { + date = new Date(date); + } + + draw_date($picker, { + "isAnim": true, + "isOutputToInputObject": true + }, date); + }; + + /* Destroy a picker */ + PickerHandler.prototype.destroy = function(){ + var $picker = this.$pickerObject; + var picker_id = $picker.data('pickerId'); + PickerObjects[picker_id] = null; + $picker.remove(); + }; + + /* Relocate a picker to position of the appended input-field. */ + PickerHandler.prototype._relocate = function(){ + var $picker = this.$pickerObject; + var $input = this.$inputObject; + + if ($input != null && $picker.data('isInline') === false) { // Float mode + // Move position of a picker - vertical + var input_outer_height = $input.outerHeight({'margin': true}); + if (!isObj('Number', input_outer_height)) { + input_outer_height = $input.outerHeight(); + } + var picker_outer_height = $picker.outerHeight({'margin': true}); + if (!isObj('Number', picker_outer_height)) { + picker_outer_height = $picker.outerHeight(); + } + + // Set width to assure date and time are side by side + if($(".datepicker_calendar", $picker).width() !== 0 && $(".datepicker_timelist", $picker).width() !== 0){ + $picker.parent().width($(".datepicker_calendar", $picker).width() + $(".datepicker_timelist", $picker).width() + 6); + } + if(parseInt($(window).height()) <= ($input.offset().top - $(document).scrollTop() + input_outer_height + picker_outer_height) ){ + // Display to top of an input-field + $picker.parent().css('top', ($input.offset().top - (input_outer_height / 2) - picker_outer_height) + 'px'); + } else { + // Display to bottom of an input-field + $picker.parent().css('top', ($input.offset().top + input_outer_height) + 'px'); + } + // Move position of a picker - horizontal + if($picker.parent().width() + $input.offset().left > $(window).width()) { + // Display left side stick to window + $picker.parent().css('left', (($(window).width() - $picker.parent().width()) / 2) + 'px'); + } else { + // Display left side stick to input + $picker.parent().css('left', $input.offset().left + 'px'); + } + // Display on most top of the z-index + $picker.parent().css('z-index', 100000); + } + }; + + /* ----- */ + + var PickerObjects = []; + var InputObjects = []; + var ActivePickerId = -1; + + var getParentPickerObject = function(obj) { + return $(obj).closest('.datepicker'); + }; + + var getPickersInputObject = function($obj) { + var $picker = getParentPickerObject($obj); + if ($picker.data("inputObjectId") != null) { + return $(InputObjects[$picker.data("inputObjectId")]); + } + return null; + }; + + var setToNow = function($obj) { + var $picker = getParentPickerObject($obj); + var date = new Date(); + draw($picker, { + "isAnim": true, + "isOutputToInputObject": true + }, date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes()); + }; + + var beforeMonth = function($obj) { + var $picker = getParentPickerObject($obj); + + if ($picker.data('stateAllowBeforeMonth') === false) { // Not allowed + return; + } + + var date = getPickedDate($picker); + var targetMonth_lastDay = new Date(date.getFullYear(), date.getMonth(), 0).getDate(); + if (targetMonth_lastDay < date.getDate()) { + date.setDate(targetMonth_lastDay); + } + draw($picker, { + "isAnim": true, + "isOutputToInputObject": true + }, date.getFullYear(), date.getMonth() - 1, date.getDate(), date.getHours(), date.getMinutes()); + + var todayDate = new Date(); + var isCurrentYear = todayDate.getFullYear() == date.getFullYear(); + var isCurrentMonth = isCurrentYear && todayDate.getMonth() == date.getMonth(); + + if (!isCurrentMonth || !$picker.data("futureOnly")) { + if (targetMonth_lastDay < date.getDate()) { + date.setDate(targetMonth_lastDay); + } + draw($picker, { + "isAnim": true, + "isOutputToInputObject": true + }, date.getFullYear(), date.getMonth() - 1, date.getDate(), date.getHours(), date.getMinutes()); + } + }; + + var nextMonth = function($obj) { + var $picker = getParentPickerObject($obj); + var date = getPickedDate($picker); + var targetMonth_lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); + if (targetMonth_lastDay < date.getDate()) { + date.setDate(targetMonth_lastDay); + } + + // Check a last date of a next month + if (getLastDate(date.getFullYear(), date.getMonth() + 1) < date.getDate()) { + date.setDate(getLastDate(date.getFullYear(), date.getMonth() + 1)); + } + + draw($picker, { + "isAnim": true, + "isOutputToInputObject": true + }, date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes()); + }; + + /** + Check a last date of a specified year and month + **/ + var getLastDate = function(year, month) { + var date = new Date(year, month + 1, 0); + return date.getDate(); + }; + + var getDateFormat = function(format, locale, is_date_only) { + if (format == "default"){ + // Default format + format = translate(locale,'format'); + if (is_date_only) { + // Convert the format to date-only (ex: YYYY/MM/DD) + format = format.substring(0, format.search(' ')); + } + } + return format; // Return date-format + }; + + var normalizeYear = function (year) { + if (year < 99) { // change year for 4 digits + var date = new Date(); + return parseInt(year) + parseInt(date.getFullYear().toString().substr(0, 2) + "00"); + } + return year; + }; + var parseDate = function (str, opt_date_format) { + var re, m, date; + if(opt_date_format != null){ + // Parse date & time with date-format + + // Match a string with date format + var df = opt_date_format.replace(/(-|\/)/g, '[-\/]') + .replace(/YYYY/gi, '(\\d{2,4})') + .replace(/(YY|MM|DD|HH|hh|mm)/g, '(\\d{1,2})') + .replace(/(M|D|H|h|m)/g, '(\\d{1,2})') + .replace(/(tt|TT)/g, '([aApP][mM])'); + re = new RegExp(df); + m = re.exec(str); + if( m != null){ + + // Generate the formats array (convert-table) + var formats = []; + var format_buf = ''; + var format_before_c = ''; + var df_ = opt_date_format; + while (df_ != null && 0 < df_.length) { + var format_c = df_.substring(0, 1); df_ = df_.substring(1, df_.length); + if (format_before_c != format_c) { + if(/(YYYY|YY|MM|DD|mm|dd|M|D|HH|H|hh|h|m|tt|TT)/.test(format_buf)){ + formats.push( format_buf ); + format_buf = ''; + } else { + format_buf = ''; + } + } + format_buf += format_c; + format_before_c = format_c; + } + if (format_buf !== '' && /(YYYY|YY|MM|DD|mm|dd|M|D|HH|H|hh|h|m|tt|TT)/.test(format_buf)){ + formats.push( format_buf ); + } + + // Convert a string (with convert-table) to a date object + var year, month, day, hour, min; + var is_successful = false; + var pm = false; + var H = false; + for(var i = 0; i < formats.length; i++){ + if(m.length < i){ + break; + } + + var f = formats[i]; + var d = m[i+1]; // Matched part of date + if(f == 'YYYY'){ + year = normalizeYear(d); + is_successful = true; + } else if(f == 'YY'){ + year = parseInt(d) + 2000; + is_successful = true; + } else if(f == 'MM' || f == 'M'){ + month = parseInt(d) - 1; + is_successful = true; + } else if(f == 'DD' || f == 'D'){ + day = d; + is_successful = true; + } else if(f == 'hh' || f == 'h'){ + hour = d; + is_successful = true; + } else if(f == 'HH' || f == 'H'){ + hour = d; + H = true; + is_successful = true; + } else if(f == 'mm' || f == 'm'){ + min = d; + is_successful = true; + } else if(f == 'tt' || f == 'TT'){ + if(d == 'pm' || d == 'PM'){ + pm = true; + } + is_successful = true; + } + } + if(H) { + if(pm) { + if(hour != 12) { + hour = parseInt(hour) + 12; + } + } else if(hour == 12) { + hour = 0; + } + } + date = new Date(year, month, day, hour, min); + + if(is_successful === true && isNaN(date) === false && isNaN(date.getDate()) === false){ // Parse successful + return date; + } + } + } + + // Parse date & time with common format + re = /^(\d{2,4})[-\/](\d{1,2})[-\/](\d{1,2}) (\d{1,2}):(\d{1,2})$/; + m = re.exec(str); + if (m !== null) { + m[1] = normalizeYear(m[1]); + date = new Date(m[1], m[2] - 1, m[3], m[4], m[5]); + } else { + // Parse for date-only + re = /^(\d{2,4})[-\/](\d{1,2})[-\/](\d{1,2})$/; + m = re.exec(str); + if(m !== null) { + m[1] = normalizeYear(m[1]); + date = new Date(m[1], m[2] - 1, m[3]); + } + } + + if(isNaN(date) === false && isNaN(date.getDate()) === false){ // Parse successful + return date; + } + return false; + }; + var getFormattedDate = function(date, date_format) { + if(date == null){ + date = new Date(); + } + + var y = date.getFullYear(); + var m = date.getMonth() + 1; + var d = date.getDate(); + var hou = date.getHours(); + var min = date.getMinutes(); + + date_format = date_format.replace(/YYYY/gi, y) + .replace(/YY/g, y - 2000)/* century */ + .replace(/MM/g, zpadding(m)) + .replace(/M/g, m) + .replace(/DD/g, zpadding(d)) + .replace(/D/g, d) + .replace(/hh/g, zpadding(hou)) + .replace(/h/g, hou) + .replace(/HH/g, (hou > 12? zpadding(hou - 12) : (hou < 1? 12 : zpadding(hou)))) + .replace(/H/g, (hou > 12? hou - 12 : (hou < 1? 12 : hou))) + .replace(/mm/g, zpadding(min)) + .replace(/m/g, min) + .replace(/tt/g, (hou >= 12? "pm" : "am")) + .replace(/TT/g, (hou >= 12? "PM" : "AM")); + return date_format; + }; + + var outputToInputObject = function($picker) { + var $inp = getPickersInputObject($picker); + if ($inp == null) { + return; + } + var date = getPickedDate($picker); + var locale = $picker.data("locale"); + var format = getDateFormat($picker.data("dateFormat"), locale, $picker.data('dateOnly')); + + var old = $inp.val(); + $inp.val(getFormattedDate(date, format)); + if (old != $inp.val()) { // only trigger if it actually changed to avoid a nasty loop condition + $inp.trigger("change"); + } + }; + + var getPickedDate = function($obj) { + var $picker = getParentPickerObject($obj); + return $picker.data("pickedDate"); + }; + + var zpadding = function(num) { + num = ("0" + num).slice(-2); + return num; + }; + + var draw_date = function($picker, option, date) { + //console.log("draw_date - " + date.toString()); + draw($picker, option, date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes()); + }; + var translate = function(locale, s) { + if (typeof lang[locale][s] !== "undefined"){ + return lang[locale][s]; + } + return lang.en[s]; + }; + var draw = function($picker, option, year, month, day, hour, min) { + var date = new Date(); + + if (hour != null) { + date = new Date(year, month, day, hour, min, 0); + } else if (year != null) { + date = new Date(year, month, day); + } else { + date = new Date(); + } + + /* Read options */ + var isTodayButton = $picker.data("todayButton"); + var isCloseButton = $picker.data("closeButton"); + var isScroll = option.isAnim; /* It same with isAnim */ + if($picker.data("timelistScroll") === false) {// If disabled by user option. + isScroll = false; + } + + var isAnim = option.isAnim; + if($picker.data("animation") === false){ // If disabled by user option. + isAnim = false; + } + + var isFutureOnly = $picker.data("futureOnly"); + var minDate = $picker.data("minDate"); + var maxDate = $picker.data("maxDate"); + + var isOutputToInputObject = option.isOutputToInputObject; + + var minuteInterval = $picker.data("minuteInterval"); + var firstDayOfWeek = $picker.data("firstDayOfWeek"); + + var allowWdays = $picker.data("allowWdays"); + if (allowWdays == null || isObj('Array', allowWdays) === false || allowWdays.length <= 0) { + allowWdays = null; + } + + var minTime = $picker.data("minTime"); + var maxTime = $picker.data("maxTime"); + + /* Check a specified date */ + var todayDate = new Date(); + if (isFutureOnly) { + if (date.getTime() < todayDate.getTime()) { // Already passed + date.setTime(todayDate.getTime()); + } + } + if(allowWdays != null && allowWdays.length <= 6) { + while (true) { + if ($.inArray(date.getDay(), allowWdays) == -1) { // Unallowed wday + // Slide a date + date.setDate(date.getDate() + 1); + } else { + break; + } + } + } + + /* Read locale option */ + var locale = $picker.data("locale"); + if (!lang.hasOwnProperty(locale)) { + locale = 'en'; + } + + /* Calculate dates */ + var firstWday = new Date(date.getFullYear(), date.getMonth(), 1).getDay() - firstDayOfWeek; + var lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); + var beforeMonthLastDay = new Date(date.getFullYear(), date.getMonth(), 0).getDate(); + var dateBeforeMonth = new Date(date.getFullYear(), date.getMonth(), 0); + var dateNextMonth = new Date(date.getFullYear(), date.getMonth() + 2, 0); + var isCurrentYear = todayDate.getFullYear() == date.getFullYear(); + var isCurrentMonth = isCurrentYear && todayDate.getMonth() == date.getMonth(); + var isCurrentDay = isCurrentMonth && todayDate.getDate() == date.getDate(); + var isNextYear = (todayDate.getFullYear() + 1 == date.getFullYear()); + var isNextMonth = (isCurrentYear && todayDate.getMonth() + 1 == date.getMonth()) || + (isNextYear && todayDate.getMonth() === 11 && date.getMonth() === 0); + var isPastMonth = false; + if (date.getFullYear() < todayDate.getFullYear() || (isCurrentYear && date.getMonth() < todayDate.getMonth())) { + isPastMonth = true; + } + + /* Collect each part */ + var $header = $picker.children('.datepicker_header'); + var $inner = $picker.children('.datepicker_inner_container'); + var $calendar = $picker.children('.datepicker_inner_container').children('.datepicker_calendar'); + var $table = $calendar.children('.datepicker_table'); + var $timelist = $picker.children('.datepicker_inner_container').children('.datepicker_timelist'); + + /* Grasp a point that will be changed */ + var changePoint = ""; + var oldDate = getPickedDate($picker); + if(oldDate != null){ + if(oldDate.getMonth() != date.getMonth() || oldDate.getDate() != date.getDate()){ + changePoint = "calendar"; + } else if (oldDate.getHours() != date.getHours() || oldDate.getMinutes() != date.getMinutes()){ + if(date.getMinutes() === 0 || date.getMinutes() % minuteInterval === 0){ + changePoint = "timelist"; + } + } + } + + /* Save newly date to Picker data */ + $($picker).data("pickedDate", date); + + /* Fade-out animation */ + if (isAnim === true) { + if(changePoint == "calendar"){ + $calendar.stop().queue([]); + $calendar.fadeTo("fast", 0.8); + }else if(changePoint == "timelist"){ + $timelist.stop().queue([]); + $timelist.fadeTo("fast", 0.8); + } + } + /* Remind timelist scroll state */ + var drawBefore_timeList_scrollTop = $timelist.scrollTop(); + + /* New timelist */ + var timelist_activeTimeCell_offsetTop = -1; + + /* Header ----- */ + $header.children().remove(); + + var cDate = new Date(date.getTime()); + cDate.setMinutes(59); + cDate.setHours(23); + cDate.setSeconds(59); + cDate.setDate(0); // last day of previous month + + var $link_before_month = null; + if ((!isFutureOnly || !isCurrentMonth) && ((minDate == null) || (minDate < cDate.getTime())) + ) { + $link_before_month = $(''); + $link_before_month.text('<'); + $link_before_month.prop('alt', translate(locale,'prevMonth')); + $link_before_month.prop('title', translate(locale,'prevMonth') ); + $link_before_month.click(function() { + beforeMonth($picker); + }); + $picker.data('stateAllowBeforeMonth', true); + } else { + $picker.data('stateAllowBeforeMonth', false); + } + + cDate.setMinutes(0); + cDate.setHours(0); + cDate.setSeconds(0); + cDate.setDate(1); // First day of next month + cDate.setMonth(date.getMonth() + 1); + + var $now_month = $(''); + $now_month.text(date.getFullYear() + " " + translate(locale, 'sep') + " " + translate(locale, 'months')[date.getMonth()]); + + var $link_next_month = null; + if ((maxDate == null) || (maxDate > cDate.getTime())) { + $link_next_month = $(''); + $link_next_month.text('>'); + $link_next_month.prop('alt', translate(locale,'nextMonth')); + $link_next_month.prop('title', translate(locale,'nextMonth')); + $link_next_month.click(function() { + nextMonth($picker); + }); + } + + if (isTodayButton) { + var $link_today = $('
'); + $link_today.addClass('icon-home'); + $link_today.prop('alt', translate(locale,'today')); + $link_today.prop('title', translate(locale,'today')); + $link_today.click(function() { + setToNow($picker); + }); + $header.append($link_today); + } + if (isCloseButton) { + var $link_close = $('
'); + $link_close.addClass('icon-close'); + $link_close.prop('alt', translate(locale,'close')); + $link_close.prop('title', translate(locale,'close')); + $link_close.click(function() { + $picker.hide(); + }); + $header.append($link_close); + } + + if ($link_before_month != null) { + $header.append($link_before_month); + } + $header.append($now_month); + if ($link_next_month != null) { + $header.append($link_next_month); + } + + /* Calendar > Table ----- */ + $table.children().remove(); + var $tr = $(''); + $table.append($tr); + + /* Output wday cells */ + var firstDayDiff = 7 + firstDayOfWeek; + var daysOfWeek = translate(locale,'days'); + var $td; + for (var i = 0; i < 7; i++) { + $td = $(''); + $td.text(daysOfWeek[((i + firstDayDiff) % 7)]); + $tr.append($td); + } + + /* Output day cells */ + var cellNum = Math.ceil((firstWday + lastDay) / 7) * 7; + i = 0; + if(firstWday < 0){ + i = -7; + } + var realDayObj = new Date(date.getTime()); + realDayObj.setHours(0); + realDayObj.setMinutes(0); + realDayObj.setSeconds(0); + for (var zz = 0; i < cellNum; i++) { + var realDay = i + 1 - firstWday; + + var isPast = isPastMonth || + (isCurrentMonth && realDay < todayDate.getDate()) || + (isNextMonth && firstWday > i && (beforeMonthLastDay + realDay) < todayDate.getDate()); + + if (i % 7 === 0) { + $tr = $(''); + $table.append($tr); + } + + $td = $(''); + $td.data("day", realDay); + + $tr.append($td); + + if (firstWday > i) {/* Before months day */ + $td.text(beforeMonthLastDay + realDay); + $td.addClass('day_another_month'); + $td.data("dateStr", dateBeforeMonth.getFullYear() + "/" + (dateBeforeMonth.getMonth() + 1) + "/" + (beforeMonthLastDay + realDay)); + realDayObj.setDate(beforeMonthLastDay + realDay); + realDayObj.setMonth(dateBeforeMonth.getMonth() ); + realDayObj.setYear(dateBeforeMonth.getFullYear() ); + } else if (i < firstWday + lastDay) {/* Now months day */ + $td.text(realDay); + $td.data("dateStr", (date.getFullYear()) + "/" + (date.getMonth() + 1) + "/" + realDay); + realDayObj.setDate( realDay ); + realDayObj.setMonth( date.getMonth() ); + realDayObj.setYear( date.getFullYear() ); + } else {/* Next months day */ + $td.text(realDay - lastDay); + $td.addClass('day_another_month'); + $td.data("dateStr", dateNextMonth.getFullYear() + "/" + (dateNextMonth.getMonth() + 1) + "/" + (realDay - lastDay)); + realDayObj.setDate( realDay - lastDay ); + realDayObj.setMonth( dateNextMonth.getMonth() ); + realDayObj.setYear( dateNextMonth.getFullYear() ); + } + + /* Check a wday */ + var wday = ((i + firstDayDiff) % 7); + if(allowWdays != null) { + if ($.inArray(wday, allowWdays) == -1) { + $td.addClass('day_in_unallowed'); + continue; // Skip + } + } else if (wday === 0) {/* Sunday */ + $td.addClass('wday_sun'); + } else if (wday == 6) {/* Saturday */ + $td.addClass('wday_sat'); + } + + /* Set a special mark class */ + if (realDay == date.getDate()) { /* selected day */ + $td.addClass('active'); + } + + if (isCurrentMonth && realDay == todayDate.getDate()) { /* today */ + $td.addClass('today'); + } + + var realDayObjMN = new Date(realDayObj.getTime()); + realDayObjMN.setHours(23); + realDayObjMN.setMinutes(59); + realDayObjMN.setSeconds(59); + + if ( + // compare to 23:59:59 on the current day (if MIN is 1pm, then we still need to show this day + ((minDate != null) && (minDate > realDayObjMN.getTime())) || ((maxDate != null) && (maxDate < realDayObj.getTime())) // compare to 00:00:00 + ) { // Out of range day + $td.addClass('out_of_range'); + } else if (isFutureOnly && isPast) { // Past day + $td.addClass('day_in_past'); + } else { + /* Set event-handler to day cell */ + $td.click(function() { + if ($(this).hasClass('hover')) { + $(this).removeClass('hover'); + } + $(this).addClass('active'); + + var $picker = getParentPickerObject($(this)); + var targetDate = new Date($(this).data("dateStr")); + var selectedDate = getPickedDate($picker); + draw($picker, { + "isAnim": false, + "isOutputToInputObject": true + }, targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate(), selectedDate.getHours(), selectedDate.getMinutes()); + if ($picker.data("dateOnly") === true && $picker.data("isInline") === false && $picker.data("closeOnSelected")){ + // Close a picker + ActivePickerId = -1; + $picker.hide(); + } + }); + + $td.hover(function() { + if (! $(this).hasClass('active')) { + $(this).addClass('hover'); + } + }, function() { + if ($(this).hasClass('hover')) { + $(this).removeClass('hover'); + } + }); + } + + /* ---- */ + } + + if ($picker.data("dateOnly") === true) { + /* dateOnly mode */ + $timelist.css("display", "none"); + } else { + /* Timelist ----- */ + $timelist.children().remove(); + + /* Set height to Timelist (Calendar innerHeight - Calendar padding) */ + if ($calendar.innerHeight() > 0) { + $timelist.css("height", $calendar.innerHeight() - 10 + 'px'); + } + + realDayObj = new Date(date.getTime()); + $timelist.css("height", $calendar.innerHeight() - 10 + 'px'); + + /* Output time cells */ + var hour_ = minTime[0]; + var min_ = minTime[1]; + + while( hour_*100+min_ < maxTime[0]*100+maxTime[1] ){ + + var $o = $('
'); + var is_past_time = hour_ < todayDate.getHours() || (hour_ == todayDate.getHours() && min_ < todayDate.getMinutes()); + var is_past = isCurrentDay && is_past_time; + + $o.addClass('timelist_item'); + var oText = ""; + if($picker.data("amPmInTimeList")){ + oText = /*zpadding*/(hour_ > 12? hour_ - 12 : (hour_ < 1? 12 : hour_)); + oText += ":" + zpadding(min_); + oText += (hour_ >= 12? "PM" : "AM"); + } else { + oText = zpadding(hour_) + ":" + zpadding(min_); + } + $o.text(oText); + $o.data("hour", hour_); + $o.data("min", min_); + + $timelist.append($o); + + realDayObj.setHours(hour_); + realDayObj.setMinutes(min_); + + if ( + ((minDate != null) && (minDate > realDayObj.getTime())) || ((maxDate != null) && (maxDate < realDayObj.getTime())) + ) { // Out of range cell + $o.addClass('out_of_range'); + } else if (isFutureOnly && is_past) { // Past cell + $o.addClass('time_in_past'); + } else { // Normal cell + /* Set event handler to time cell */ + $o.click(function() { + if ($(this).hasClass('hover')) { + $(this).removeClass('hover'); + } + $(this).addClass('active'); + + var $picker = getParentPickerObject($(this)); + var date = getPickedDate($picker); + var hour = $(this).data("hour"); + var min = $(this).data("min"); + draw($picker, { + "isAnim": false, + "isOutputToInputObject": true + }, date.getFullYear(), date.getMonth(), date.getDate(), hour, min); + + if ($picker.data("isInline") === false && $picker.data("closeOnSelected")){ + // Close a picker + ActivePickerId = -1; + $picker.hide(); + } + }); + + $o.hover(function() { + if (! $(this).hasClass('active')) { + $(this).addClass('hover'); + } + }, function() { + if ($(this).hasClass('hover')) { + $(this).removeClass('hover'); + } + }); + } + + if (hour_ == date.getHours() && min_ == date.getMinutes()) { /* selected time */ + $o.addClass('active'); + timelist_activeTimeCell_offsetTop = $o.offset().top; + } + + min_ += minuteInterval; + if (min_ >= 60){ + min_ = min_ - 60; + hour_++; + } + + } + + /* Scroll the timelist */ + if(isScroll === true){ + /* Scroll to new active time-cell position */ + $timelist.scrollTop(timelist_activeTimeCell_offsetTop - $timelist.offset().top); + }else{ + /* Scroll to position that before redraw. */ + $timelist.scrollTop(drawBefore_timeList_scrollTop); + } + } + + /* Fade-in animation */ + if (isAnim === true) { + if(changePoint == "calendar"){ + $calendar.fadeTo("fast", 1.0); + }else if(changePoint == "timelist"){ + $timelist.fadeTo("fast", 1.0); + } + } + + /* Output to InputForm */ + if (isOutputToInputObject === true) { + outputToInputObject($picker); + } + }; + + /* Check for object type */ + var isObj = function(type, obj) { + /* http://qiita.com/Layzie/items/465e715dae14e2f601de */ + var clas = Object.prototype.toString.call(obj).slice(8, -1); + return obj !== undefined && obj !== null && clas === type; + }; + + var init = function($obj, opt) { + /* Container */ + var $picker = $('
'); + + $picker.destroy = function() { + window.alert('destroy!'); + }; + + $picker.addClass('datepicker'); + $obj.append($picker); + + /* Set current date */ + if(!opt.current) { + opt.current = new Date(); + } else { + var format = getDateFormat(opt.dateFormat, opt.locale, opt.dateOnly); + var date = parseDate(opt.current, format); + if (date) { + opt.current = date; + } else { + opt.current = new Date(); + } + } + + /* Set options data to container object */ + if (opt.inputObjectId != null) { + $picker.data("inputObjectId", opt.inputObjectId); + } + $picker.data("dateOnly", opt.dateOnly); + $picker.data("pickerId", PickerObjects.length); + $picker.data("dateFormat", opt.dateFormat); + $picker.data("locale", opt.locale); + $picker.data("firstDayOfWeek", opt.firstDayOfWeek); + $picker.data("animation", opt.animation); + $picker.data("closeOnSelected", opt.closeOnSelected); + $picker.data("timelistScroll", opt.timelistScroll); + $picker.data("calendarMouseScroll", opt.calendarMouseScroll); + $picker.data("todayButton", opt.todayButton); + $picker.data("closeButton", opt.closeButton); + $picker.data('futureOnly', opt.futureOnly); + $picker.data('onShow', opt.onShow); + $picker.data('onHide', opt.onHide); + $picker.data('onInit', opt.onInit); + $picker.data('allowWdays', opt.allowWdays); + + if(opt.amPmInTimeList === true){ + $picker.data('amPmInTimeList', true); + } else { + $picker.data('amPmInTimeList', false); + } + + var minDate = Date.parse(opt.minDate); + if (isNaN(minDate)) { // invalid date? + $picker.data('minDate', null); // set to null + } else { + $picker.data('minDate', minDate); + } + + var maxDate = Date.parse(opt.maxDate); + if (isNaN(maxDate)) { // invalid date? + $picker.data('maxDate', null); // set to null + } else { + $picker.data('maxDate', maxDate); + } + $picker.data("state", 0); + + if( 5 <= opt.minuteInterval && opt.minuteInterval <= 30 ){ + $picker.data("minuteInterval", opt.minuteInterval); + } else { + $picker.data("minuteInterval", 30); + } + opt.minTime = opt.minTime.split(':'); + opt.maxTime = opt.maxTime.split(':'); + + if(! ((opt.minTime[0] >= 0 ) && (opt.minTime[0] <24 ))){ + opt.minTime[0]="00"; + } + if(! ((opt.maxTime[0] >= 0 ) && (opt.maxTime[0] <24 ))){ + opt.maxTime[0]="23"; + } + if(! ((opt.minTime[1] >= 0 ) && (opt.minTime[1] <60 ))){ + opt.minTime[1]="00"; + } + if(! ((opt.maxTime[1] >= 0 ) && (opt.maxTime[1] <24 ))){ + opt.maxTime[1]="59"; + } + opt.minTime[0]=parseInt(opt.minTime[0]); + opt.minTime[1]=parseInt(opt.minTime[1]); + opt.maxTime[0]=parseInt(opt.maxTime[0]); + opt.maxTime[1]=parseInt(opt.maxTime[1]); + $picker.data('minTime', opt.minTime); + $picker.data('maxTime', opt.maxTime); + + /* Header */ + var $header = $('
'); + $header.addClass('datepicker_header'); + $picker.append($header); + /* InnerContainer*/ + var $inner = $('
'); + $inner.addClass('datepicker_inner_container'); + $picker.append($inner); + /* Calendar */ + var $calendar = $('
'); + $calendar.addClass('datepicker_calendar'); + var $table = $(''); + $table.addClass('datepicker_table'); + $calendar.append($table); + $inner.append($calendar); + /* Timelist */ + var $timelist = $('
'); + $timelist.addClass('datepicker_timelist'); + $inner.append($timelist); + + /* Set event handler to picker */ + $picker.hover( + function(){ + ActivePickerId = $(this).data("pickerId"); + }, + function(){ + ActivePickerId = -1; + } + ); + + /* Set event-handler to calendar */ + if (opt.calendarMouseScroll) { + if (window.sidebar) { // Mozilla Firefox + $calendar.bind('DOMMouseScroll', function(e){ // Change a month with mouse wheel scroll for Fx + var $picker = getParentPickerObject($(this)); + + // up,left [delta < 0] down,right [delta > 0] + var delta = e.originalEvent.detail; + /* + // this code need to be commented - it's seems to be unnecessary + // normalization (/3) is not needed as we move one month back or forth + if(e.originalEvent.axis !== undefined && e.originalEvent.axis == e.originalEvent.HORIZONTAL_AXIS){ + e.deltaX = delta; + e.deltaY = 0; + } else { + e.deltaX = 0; + e.deltaY = delta; + } + e.deltaX /= 3; + e.deltaY /= 3; + */ + if(delta > 0) { + nextMonth($picker); + } else { + beforeMonth($picker); + } + return false; + }); + } else { // Other browsers + $calendar.bind('mousewheel', function(e){ // Change a month with mouse wheel scroll + var $picker = getParentPickerObject($(this)); + // up [delta > 0] down [delta < 0] + if(e.originalEvent.wheelDelta /120 > 0) { + beforeMonth($picker); + } else { + nextMonth($picker); + } + return false; + }); + } + } + + PickerObjects.push($picker); + + draw_date($picker, { + "isAnim": true, + "isOutputToInputObject": opt.autodateOnStart + }, opt.current); + }; + + var getDefaults = function() { + return { + "current": null, + "dateFormat": "default", + "locale": "en", + "animation": true, + "minuteInterval": 30, + "firstDayOfWeek": 0, + "closeOnSelected": false, + "timelistScroll": true, + "calendarMouseScroll": true, + "todayButton": true, + "closeButton": true, + "dateOnly": false, + "futureOnly": false, + "minDate" : null, + "maxDate" : null, + "autodateOnStart": true, + "minTime":"00:00", + "maxTime":"23:59", + "onShow": null, + "onHide": null, + "allowWdays": null, + "amPmInTimeList": false + }; + }; + + /** + * Initialize dtpicker + */ + $.fn.dtpicker = function(config) { + var date = new Date(); + var defaults = getDefaults(); + + if(typeof config === "undefined" || config.closeButton !== true){ + defaults.closeButton = false; + } + + defaults.inputObjectId = undefined; + var options = $.extend(defaults, config); + + return this.each(function(i) { + init($(this), options); + }); + }; + + /** + * Initialize dtpicker, append to Text input field + * */ + $.fn.appendDtpicker = function(config) { + var date = new Date(); + var defaults = getDefaults(); + + if(typeof config !== "undefined" && config.inline === true && config.closeButton !== true){ + defaults.closeButton = false; + } + + defaults.inline = false; + var options = $.extend(defaults, config); + + return this.each(function(i) { + /* Checking exist a picker */ + var input = this; + if(0 < $(PickerObjects[$(input).data('pickerId')]).length) { + console.log("dtpicker - Already exist appended picker"); + return; + } + + /* Add input-field with inputsObjects array */ + var inputObjectId = InputObjects.length; + InputObjects.push(input); + + options.inputObjectId = inputObjectId; + + /* Current date */ + var date, strDate, strTime; + if($(input).val() != null && $(input).val() !== ""){ + options.current = $(input).val(); + } + + /* Make parent-div for picker */ + var $d = $('
'); + if(options.inline){ // Inline mode + $d.insertAfter(input); + } else { // Float mode + $d.css("position","absolute"); + $('body').append($d); + } + + /* Initialize picker */ + + var pickerId = PickerObjects.length; + + var $picker_parent = $($d).dtpicker(options); // call dtpicker() method + + var $picker = $picker_parent.children('.datepicker'); + + /* Link input-field with picker*/ + $(input).data('pickerId', pickerId); + + /* Set event handler to input-field */ + + $(input).keyup(function() { + var $input = $(this); + var $picker = $(PickerObjects[$input.data('pickerId')]); + if ($input.val() != null && ( + $input.data('beforeVal') == null || + ( $input.data('beforeVal') != null && $input.data('beforeVal') != $input.val()) ) + ) { /* beforeValue == null || beforeValue != nowValue */ + var format = getDateFormat($picker.data('dateFormat'), $picker.data('locale'), $picker.data('dateOnly')); + var date = parseDate($input.val(), format); + //console.log("dtpicker - inputKeyup - format: " + format + ", date: " + $input.val() + " -> " + date); + if (date) { + draw_date($picker, { + "isAnim":true, + "isOutputToInputObject":false + }, date); + } + } + $input.data('beforeVal', $input.val()); + }); + + $(input).change(function(){ + $(this).trigger('keyup'); + }); + + var handler = new PickerHandler($picker, $(input)); + + if(options.inline === true){ + /* inline mode */ + $picker.data('isInline',true); + } else { + /* float mode */ + $picker.data('isInline',false); + $picker_parent.css({ + "zIndex": 100 + }); + $picker.css("width","auto"); + + /* Hide this picker */ + $picker.hide(); + + /* Set onClick event handler for input-field */ + $(input).on('click, focus',function(){ + var $input = $(this); + var $picker = $(PickerObjects[$input.data('pickerId')]); + + // Generate the handler of a picker + var handler = new PickerHandler($picker, $input); + // Get the display state of a picker + var is_showed = handler.isShow(); + if (!is_showed) { + // Show a picker + handler.show(); + + // Call a event-hanlder + var func = $picker.data('onShow'); + if (func != null) { + console.log("dtpicker- Call the onShow handler"); + func(handler); + } + } + }); + + // Set an event handler for resizing of a window + (function(handler){ + $(window).resize(function(){ + handler._relocate(); + }); + $(window).scroll(function(){ + handler._relocate(); + }); + })(handler); + } + + // Set an event handler for removing of an input-field + $(input).bind('destroyed', function() { + var $input = $(this); + var $picker = $(PickerObjects[$input.data('pickerId')]); + // Generate the handler of a picker + var handler = new PickerHandler($picker, $input); + // Destroy a picker + handler.destroy(); + }); + + // Call a event-handler + var func = $picker.data('onInit'); + if (func != null) { + console.log("dtpicker- Call the onInit handler"); + func(handler); + } + }); + }; + + /** + * Handle a appended dtpicker + * */ + var methods = { + show : function( ) { + var $input = $(this); + var $picker = $(PickerObjects[$input.data('pickerId')]); + if ($picker != null) { + var handler = new PickerHandler($picker, $input); + // Show a picker + handler.show(); + } + }, + hide : function( ) { + var $input = $(this); + var $picker = $(PickerObjects[$input.data('pickerId')]); + if ($picker != null) { + var handler = new PickerHandler($picker, $input); + // Hide a picker + handler.hide(); + } + }, + setDate : function( date ) { + var $input = $(this); + var $picker = $(PickerObjects[$input.data('pickerId')]); + if ($picker != null) { + var handler = new PickerHandler($picker, $input); + // Set a date + handler.setDate(date); + } + }, + getDate : function( ) { + var $input = $(this); + var $picker = $(PickerObjects[$input.data('pickerId')]); + if ($picker != null) { + var handler = new PickerHandler($picker, $input); + // Get a date + return handler.getDate(); + } + }, + destroy : function( ) { + var $input = $(this); + var $picker = $(PickerObjects[$input.data('pickerId')]); + if ($picker != null) { + var handler = new PickerHandler($picker, $input); + // Destroy a picker + handler.destroy(); + } + } + }; + + $.fn.handleDtpicker = 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.handleDtpicker' ); + } + }; + + if (!window.console) { // Not available a console on this environment. + window.console = {}; + window.console.log = function(){}; + } + + /* Define a special event for catch when destroy of an input-field. */ + $.event.special.destroyed = { + remove: function(o) { + if (o.handler) { + o.handler.apply(this, arguments); + } + } + }; + + /* Set event handler to Body element, for hide a floated-picker */ + $(function(){ + $('body').click(function(){ + for(var i=0;i