
add duedate in issue
@134d0391d0af29713d8bd5bb55d325338607f0b3
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
... | ... | @@ -3195,6 +3195,9 @@ |
3195 | 3195 |
width: 100%; |
3196 | 3196 |
} |
3197 | 3197 |
} |
3198 |
+ span { |
|
3199 |
+ &.over { color:#f36c22; } |
|
3200 |
+ } |
|
3198 | 3201 |
} |
3199 | 3202 |
.label-editor { |
3200 | 3203 |
padding-top:10px; |
... | ... | @@ -3457,6 +3460,8 @@ |
3457 | 3460 |
|
3458 | 3461 |
.bar { height:100%; } |
3459 | 3462 |
} |
3463 |
+ |
|
3464 |
+ &.over { color:#f36c22; } |
|
3460 | 3465 |
} |
3461 | 3466 |
} |
3462 | 3467 |
} |
... | ... | @@ -3482,19 +3487,18 @@ |
3482 | 3487 |
} |
3483 | 3488 |
|
3484 | 3489 |
.mileston-tag { |
3485 |
- margin-top:7px; |
|
3486 |
- margin-right: 25px; |
|
3487 | 3490 |
font-weight: bold; |
3488 | 3491 |
max-width: 135px; |
3489 | 3492 |
text-overflow: ellipsis; |
3490 | 3493 |
white-space: nowrap; |
3491 | 3494 |
overflow: hidden; |
3492 | 3495 |
color: darken(@secondary, 10%); |
3493 |
- padding: 3px 10px; |
|
3494 |
- background: #f7f7f7; |
|
3495 | 3496 |
font-size:11px; |
3496 | 3497 |
.border-radius(6px); |
3497 |
- .box-shadow(1px 1px 1px rgba(0,0,0,0.15)); |
|
3498 |
+} |
|
3499 |
+ |
|
3500 |
+.overdue { |
|
3501 |
+ color:@yobi-red; |
|
3498 | 3502 |
} |
3499 | 3503 |
|
3500 | 3504 |
.pullRequest-branchInfo { |
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
... | ... | @@ -352,6 +352,11 @@ |
352 | 352 |
} |
353 | 353 |
} |
354 | 354 |
|
355 |
+ if (issueMassUpdate.isDueDateChanged) { |
|
356 |
+ issue.dueDate = issueMassUpdate.dueDate; |
|
357 |
+ issue.expandDueDate(); |
|
358 |
+ } |
|
359 |
+ |
|
355 | 360 |
issue.updatedDate = JodaDateUtil.now(); |
356 | 361 |
issue.update(); |
357 | 362 |
updatedItems++; |
... | ... | @@ -408,6 +413,7 @@ |
408 | 413 |
addLabels(newIssue, request()); |
409 | 414 |
setMilestone(issueForm, newIssue); |
410 | 415 |
|
416 |
+ newIssue.expandDueDate(); |
|
411 | 417 |
newIssue.save(); |
412 | 418 |
|
413 | 419 |
attachUploadFilesToPost(newIssue.asResource()); |
... | ... | @@ -497,6 +503,7 @@ |
497 | 503 |
final Issue issue = issueForm.get(); |
498 | 504 |
removeAnonymousAssignee(issue); |
499 | 505 |
setMilestone(issueForm, issue); |
506 |
+ issue.expandDueDate(); |
|
500 | 507 |
|
501 | 508 |
final Issue originalIssue = Issue.findByNumber(project, number); |
502 | 509 |
|
--- app/models/Issue.java
+++ app/models/Issue.java
... | ... | @@ -32,14 +32,19 @@ |
32 | 32 |
import models.enumeration.State; |
33 | 33 |
import models.resource.Resource; |
34 | 34 |
import models.support.SearchCondition; |
35 |
+import org.apache.commons.lang3.time.DateUtils; |
|
36 |
+import org.joda.time.*; |
|
37 |
+import org.joda.time.DateTime; |
|
38 |
+import play.data.format.Formats; |
|
39 |
+import play.i18n.Messages; |
|
35 | 40 |
import utils.JodaDateUtil; |
36 | 41 |
|
37 | 42 |
import javax.persistence.*; |
38 | 43 |
|
39 |
-import org.apache.commons.lang3.StringUtils; |
|
40 |
- |
|
41 | 44 |
import java.io.ByteArrayOutputStream; |
42 | 45 |
import java.io.IOException; |
46 |
+import java.lang.Boolean; |
|
47 |
+import java.text.SimpleDateFormat; |
|
43 | 48 |
import java.util.*; |
44 | 49 |
import java.util.regex.Pattern; |
45 | 50 |
import com.avaje.ebean.Page; |
... | ... | @@ -61,6 +66,9 @@ |
61 | 66 |
public static final Pattern ISSUE_PATTERN = Pattern.compile("#\\d+"); |
62 | 67 |
|
63 | 68 |
public State state; |
69 |
+ |
|
70 |
+ @Formats.DateTime(pattern = "yyyy-MM-dd") |
|
71 |
+ public Date dueDate; |
|
64 | 72 |
|
65 | 73 |
public static List<State> availableStates = new ArrayList<>(); |
66 | 74 |
static { |
... | ... | @@ -146,6 +154,14 @@ |
146 | 154 |
} |
147 | 155 |
if(!updateProps.isEmpty()) { |
148 | 156 |
Ebean.update(this, updateProps); |
157 |
+ } |
|
158 |
+ } |
|
159 |
+ |
|
160 |
+ public void expandDueDate() { |
|
161 |
+ if (dueDate != null) { |
|
162 |
+ dueDate = DateUtils.setHours(dueDate, 23); |
|
163 |
+ dueDate = DateUtils.setMinutes(dueDate, 59); |
|
164 |
+ dueDate = DateUtils.setSeconds(dueDate, 59); |
|
149 | 165 |
} |
150 | 166 |
} |
151 | 167 |
|
... | ... | @@ -439,4 +455,32 @@ |
439 | 455 |
public boolean isVotedBy(User user) { |
440 | 456 |
return this.voters.contains(user); |
441 | 457 |
} |
458 |
+ |
|
459 |
+ public String getDueDateString() { |
|
460 |
+ if (dueDate == null) { |
|
461 |
+ return null; |
|
462 |
+ } |
|
463 |
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); |
|
464 |
+ return sdf.format(this.dueDate); |
|
465 |
+ } |
|
466 |
+ |
|
467 |
+ public Boolean isOverDueDate(){ |
|
468 |
+ return (JodaDateUtil.ago(dueDate).getMillis() > 0); |
|
469 |
+ } |
|
470 |
+ |
|
471 |
+ public String until(){ |
|
472 |
+ if (dueDate == null) { |
|
473 |
+ return null; |
|
474 |
+ } |
|
475 |
+ |
|
476 |
+ Date now = JodaDateUtil.now(); |
|
477 |
+ |
|
478 |
+ if (DateUtils.isSameDay(now, dueDate)) { |
|
479 |
+ return Messages.get("common.time.today"); |
|
480 |
+ } else if (isOverDueDate()) { |
|
481 |
+ return Messages.get("common.time.default.day", JodaDateUtil.localDaysBetween(dueDate, now)); |
|
482 |
+ } else { |
|
483 |
+ return Messages.get("common.time.default.day", JodaDateUtil.localDaysBetween(now, dueDate)); |
|
484 |
+ } |
|
485 |
+ } |
|
442 | 486 |
} |
--- app/models/IssueMassUpdate.java
+++ app/models/IssueMassUpdate.java
... | ... | @@ -21,8 +21,10 @@ |
21 | 21 |
package models; |
22 | 22 |
|
23 | 23 |
import models.enumeration.State; |
24 |
+import play.data.format.Formats; |
|
24 | 25 |
import play.data.validation.Constraints; |
25 | 26 |
|
27 |
+import java.util.Date; |
|
26 | 28 |
import java.util.List; |
27 | 29 |
|
28 | 30 |
public class IssueMassUpdate { |
... | ... | @@ -30,6 +32,9 @@ |
30 | 32 |
public User assignee; |
31 | 33 |
public Milestone milestone; |
32 | 34 |
public boolean delete; |
35 |
+ @Formats.DateTime(pattern = "yyyy-MM-dd") |
|
36 |
+ public Date dueDate; |
|
37 |
+ public boolean isDueDateChanged; |
|
33 | 38 |
|
34 | 39 |
@Constraints.Required |
35 | 40 |
public List<Issue> issues; |
--- app/models/support/SearchCondition.java
+++ app/models/support/SearchCondition.java
... | ... | @@ -27,13 +27,13 @@ |
27 | 27 |
import models.enumeration.State; |
28 | 28 |
import org.apache.commons.collections.CollectionUtils; |
29 | 29 |
import org.apache.commons.lang3.StringUtils; |
30 |
+import org.apache.commons.lang3.time.DateUtils; |
|
31 |
+import play.data.format.Formats; |
|
30 | 32 |
import utils.LabelSearchUtil; |
31 | 33 |
|
32 | 34 |
import javax.persistence.Transient; |
33 |
-import java.util.ArrayList; |
|
34 |
-import java.util.HashSet; |
|
35 |
-import java.util.List; |
|
36 |
-import java.util.Set; |
|
35 |
+import java.text.SimpleDateFormat; |
|
36 |
+import java.util.*; |
|
37 | 37 |
|
38 | 38 |
import static models.enumeration.ResourceType.*; |
39 | 39 |
|
... | ... | @@ -49,6 +49,9 @@ |
49 | 49 |
public Project project; |
50 | 50 |
|
51 | 51 |
public Long mentionId; |
52 |
+ |
|
53 |
+ @Formats.DateTime(pattern = "yyyy-MM-dd") |
|
54 |
+ public Date dueDate; |
|
52 | 55 |
|
53 | 56 |
/** |
54 | 57 |
* This doesn't copy {@code pageNum}, because it is safe when changing tabs with page parameter. |
... | ... | @@ -67,6 +70,7 @@ |
67 | 70 |
one.authorId = this.authorId; |
68 | 71 |
one.assigneeId = this.assigneeId; |
69 | 72 |
one.mentionId = this.mentionId; |
73 |
+ one.dueDate = this.dueDate; |
|
70 | 74 |
return one; |
71 | 75 |
} |
72 | 76 |
|
... | ... | @@ -199,6 +203,10 @@ |
199 | 203 |
el.orderBy(orderBy + " " + orderDir); |
200 | 204 |
} |
201 | 205 |
|
206 |
+ if (dueDate != null) { |
|
207 |
+ el.lt("dueDate", DateUtils.addDays(dueDate, 1)); |
|
208 |
+ } |
|
209 |
+ |
|
202 | 210 |
return el; |
203 | 211 |
} |
204 | 212 |
|
... | ... | @@ -301,6 +309,18 @@ |
301 | 309 |
el.orderBy(orderBy + " " + orderDir); |
302 | 310 |
} |
303 | 311 |
|
312 |
+ if (dueDate != null) { |
|
313 |
+ el.lt("dueDate", DateUtils.addDays(dueDate, 1)); |
|
314 |
+ } |
|
315 |
+ |
|
304 | 316 |
return el; |
305 | 317 |
} |
318 |
+ |
|
319 |
+ public String getDueDateString() { |
|
320 |
+ if (dueDate == null) { |
|
321 |
+ return null; |
|
322 |
+ } |
|
323 |
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); |
|
324 |
+ return sdf.format(this.dueDate); |
|
325 |
+ } |
|
306 | 326 |
} |
--- app/utils/JodaDateUtil.java
+++ app/utils/JodaDateUtil.java
... | ... | @@ -83,4 +83,7 @@ |
83 | 83 |
return moment.invoke("fromNow"); |
84 | 84 |
} |
85 | 85 |
|
86 |
+ public static int localDaysBetween(Date from, Date to) { |
|
87 |
+ return Days.daysBetween(new DateTime(from).toLocalDate(), new DateTime(to).toLocalDate()).getDays(); |
|
88 |
+ } |
|
86 | 89 |
} |
+++ app/views/common/calendar.scala.html
... | ... | @@ -0,0 +1,24 @@ |
1 | +@** | |
2 | +* Yobi, Project Hosting SW | |
3 | +* | |
4 | +* Copyright 2014 NAVER Corp. | |
5 | +* http://yobi.io | |
6 | +* | |
7 | +* @Author Changgun Kim | |
8 | +* | |
9 | +* Licensed under the Apache License, Version 2.0 (the "License"); | |
10 | +* you may not use this file except in compliance with the License. | |
11 | +* You may obtain a copy of the License at | |
12 | +* | |
13 | +* http://www.apache.org/licenses/LICENSE-2.0 | |
14 | +* | |
15 | +* Unless required by applicable law or agreed to in writing, software | |
16 | +* distributed under the License is distributed on an "AS IS" BASIS, | |
17 | +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | +* See the License for the specific language governing permissions and | |
19 | +* limitations under the License. | |
20 | +**@ | |
21 | + | |
22 | +<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/moment-with-langs.min.js")"></script> | |
23 | +<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/pikaday/pikaday.js")"></script> | |
24 | +<script type="text/javascript" src="@routes.Assets.at("javascripts/common/yobi.ui.Calendar.js")"></script> |
--- app/views/issue/create.scala.html
+++ app/views/issue/create.scala.html
... | ... | @@ -106,6 +106,16 @@ |
106 | 106 |
</dl> |
107 | 107 |
} |
108 | 108 |
|
109 |
+ <dl class="issue-option"> |
|
110 |
+ <dt>@Messages("issue.dueDate")</dt> |
|
111 |
+ <dd> |
|
112 |
+ <div class="search search-bar"> |
|
113 |
+ <input type="text" id="issueDueDate" data-toggle="calendar" name="dueDate" class="textbox full"> |
|
114 |
+ <button type="button" class="search-btn"><i class="yobicon-calendar2"></i></button> |
|
115 |
+ </div> |
|
116 |
+ </dd> |
|
117 |
+ </dl> |
|
118 |
+ |
|
109 | 119 |
<dl id="labels" class="issue-option"> |
110 | 120 |
<dt> |
111 | 121 |
@Messages("label") |
... | ... | @@ -127,25 +137,26 @@ |
127 | 137 |
@common.markdown(project) |
128 | 138 |
@common.issueLabel() |
129 | 139 |
@common.select2() |
140 |
+@common.calendar() |
|
130 | 141 |
<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")"> |
131 | 142 |
<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script> |
132 | 143 |
<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script> |
133 | 144 |
<script type="text/javascript"> |
134 |
- $(document).ready(function(){ |
|
135 |
- // yobi.Label |
|
145 |
+ $(document).ready(function(){ |
|
146 |
+ // yobi.Label |
|
136 | 147 |
yobi.Label.init({ |
137 | 148 |
"sURLLabels": "@routes.IssueLabelApp.labels(project.owner, project.name)", |
138 | 149 |
"sURLPost" : "@routes.IssueLabelApp.newLabel(project.owner, project.name)" |
139 | 150 |
}); |
140 | 151 |
|
141 | 152 |
// yobi.issue.Write |
142 |
- $yobi.loadModule("issue.Write", { |
|
143 |
- "sMode" : "new", |
|
153 |
+ $yobi.loadModule("issue.Write", { |
|
154 |
+ "sMode": "new", |
|
144 | 155 |
"sIssueFormURL": "@routes.IssueApp.newIssueForm(project.owner, project.name)", |
145 |
- "sIssueListURL": "@routes.IssueApp.issues(project.owner, project.name)", |
|
146 |
- "elTextarea" : 'textarea[data-editor-mode="content-body"]', |
|
147 |
- "elMilestoneRefresh": $("#milestoneOption .yobicon-refresh") |
|
148 |
- }); |
|
156 |
+ "sIssueListURL": "@routes.IssueApp.issues(project.owner, project.name)", |
|
157 |
+ "elTextarea": 'textarea[data-editor-mode="content-body"]', |
|
158 |
+ "elMilestoneRefresh": $("#milestoneOption .yobicon-refresh"), |
|
159 |
+ }); |
|
149 | 160 |
|
150 | 161 |
// yobi.Mention |
151 | 162 |
yobi.Mention({ |
--- app/views/issue/edit.scala.html
+++ app/views/issue/edit.scala.html
... | ... | @@ -144,6 +144,16 @@ |
144 | 144 |
</dl> |
145 | 145 |
} |
146 | 146 |
|
147 |
+ <dl class="issue-option"> |
|
148 |
+ <dt>@Messages("issue.dueDate")</dt> |
|
149 |
+ <dd> |
|
150 |
+ <div class="search search-bar"> |
|
151 |
+ <input type="text" id="issueDueDate" data-toggle="calendar" name="dueDate" class="textbox full" value="@issue.getDueDateString"> |
|
152 |
+ <button type="button" class="search-btn"><i class="yobicon-calendar2"></i></button> |
|
153 |
+ </div> |
|
154 |
+ </dd> |
|
155 |
+ </dl> |
|
156 |
+ |
|
147 | 157 |
<dl id="labels" class="issue-option"> |
148 | 158 |
<dt> |
149 | 159 |
@Messages("label") |
... | ... | @@ -165,6 +175,7 @@ |
165 | 175 |
@common.markdown(project) |
166 | 176 |
@common.issueLabel() |
167 | 177 |
@common.select2() |
178 |
+@common.calendar() |
|
168 | 179 |
<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")"> |
169 | 180 |
<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script> |
170 | 181 |
<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script> |
--- app/views/issue/list.scala.html
+++ app/views/issue/list.scala.html
... | ... | @@ -29,6 +29,7 @@ |
29 | 29 |
</div> |
30 | 30 |
</div> |
31 | 31 |
@common.select2() |
32 |
+@common.calendar() |
|
32 | 33 |
<script type="text/javascript"> |
33 | 34 |
$(document).ready(function(){ |
34 | 35 |
var htPjaxOptions = { |
--- app/views/issue/my_partial_list.scala.html
+++ app/views/issue/my_partial_list.scala.html
... | ... | @@ -32,7 +32,7 @@ |
32 | 32 |
@for(issue <- issueList){ |
33 | 33 |
@defining(User.findByLoginId(issue.authorLoginId)){ user => |
34 | 34 |
<li class="post-item" id="issue-item-@issue.id"> |
35 |
- <div class="span9"> |
|
35 |
+ <div class="span10"> |
|
36 | 36 |
<a href="@routes.UserApp.userInfo(user.loginId)" class="avatar-wrap mlarge" data-toggle="tooltip" data-placement="top" title="@user.loginId"> |
37 | 37 |
@if(user.avatarUrl == UserApp.DEFAULT_AVATAR_URL){ |
38 | 38 |
<img src="@urlToPicture(user.email, 32)"> |
... | ... | @@ -62,6 +62,14 @@ |
62 | 62 |
@agoOrDateString(issue.createdDate) |
63 | 63 |
</span> |
64 | 64 |
|
65 |
+ @if(issue.milestone != null && issue.project != null && issue.project.menuSetting != null && issue.project.menuSetting.milestone) { |
|
66 |
+ <span class="infos-item mileston-tag"> |
|
67 |
+ <a href="@routes.MilestoneApp.milestone(issue.project.owner, issue.project.name, issue.milestone.id)" data-toggle="tooltip" data-placement="top" title="@Messages("milestone")"> |
|
68 |
+ @issue.milestone.title |
|
69 |
+ </a> |
|
70 |
+ </span> |
|
71 |
+ } |
|
72 |
+ |
|
65 | 73 |
@if(issue.comments.size()>0 || issue.voters.size()>0) { |
66 | 74 |
<span class="infos-item item-count-groups"> |
67 | 75 |
@if(issue.comments.size()>0){ |
... | ... | @@ -78,7 +86,7 @@ |
78 | 86 |
} |
79 | 87 |
</div> |
80 | 88 |
</div> |
81 |
- <div class="span3"> |
|
89 |
+ <div class="span2"> |
|
82 | 90 |
<div class="mt5 pull-right"> |
83 | 91 |
@if(issue.assigneeName != null) { |
84 | 92 |
<a href="@routes.UserApp.userInfo(issue.assignee.user.loginId)" class="avatar-wrap assinee" data-toggle="tooltip" data-placement="top" title="@Messages("issue.assignee"): @issue.assigneeName"> |
... | ... | @@ -88,15 +96,16 @@ |
88 | 96 |
<div class="empty-avatar-wrap"> </div> |
89 | 97 |
} |
90 | 98 |
</div> |
91 |
- <div class="pull-right"> |
|
92 |
- @if(issue.milestone != null && issue.project != null && issue.project.menuSetting != null && issue.project.menuSetting.milestone) { |
|
93 |
- <div class="mileston-tag"> |
|
94 |
- <a href="@routes.MilestoneApp.milestone(issue.project.owner, issue.project.name, issue.milestone.id)" data-toggle="tooltip" data-placement="top" title="@Messages("milestone")"> |
|
95 |
- @issue.milestone.title |
|
96 |
- </a> |
|
97 |
- </div> |
|
99 |
+ @if(issue.dueDate != null) { |
|
100 |
+ <div class="mr20 mt10 pull-right @if(issue.isOverDueDate) {overdue}" data-toggle="tooltip" data-placement="top" title="@issue.getDueDateString"> |
|
101 |
+ <i class="yobicon-clock2"></i> |
|
102 |
+ @if(issue.isOverDueDate) { |
|
103 |
+ @Messages("issue.dueDate.overdue") |
|
104 |
+ } else { |
|
105 |
+ @issue.until |
|
98 | 106 |
} |
99 | 107 |
</div> |
108 |
+ } |
|
100 | 109 |
</div> |
101 | 110 |
</li> |
102 | 111 |
} |
--- app/views/issue/my_partial_search.scala.html
+++ app/views/issue/my_partial_search.scala.html
... | ... | @@ -109,6 +109,7 @@ |
109 | 109 |
<div class="filter-wrap small-heights"> |
110 | 110 |
@if(currentPage.getList.size() > 1){ |
111 | 111 |
<div class="filters pull-right"> |
112 |
+ @makeFilterLink("dueDate", param.orderBy, param.orderDir, Messages("common.order.dueDate")) |
|
112 | 113 |
@makeFilterLink("updatedDate", param.orderBy, param.orderDir, Messages("common.order.updatedDate")) |
113 | 114 |
@makeFilterLink("createdDate", param.orderBy, param.orderDir, Messages("common.order.date")) |
114 | 115 |
@makeFilterLink("numOfComments", param.orderBy, param.orderDir, Messages("common.order.comments")) |
--- app/views/issue/partial_list.scala.html
+++ app/views/issue/partial_list.scala.html
... | ... | @@ -65,6 +65,14 @@ |
65 | 65 |
@agoOrDateString(issue.createdDate) |
66 | 66 |
</span> |
67 | 67 |
|
68 |
+ @if(project.menuSetting.milestone && issue.milestone != null) { |
|
69 |
+ <span class="infos-item mileston-tag"> |
|
70 |
+ <a href="@routes.MilestoneApp.milestone(project.owner, project.name, issue.milestone.id)" data-toggle="tooltip" data-placement="top" title="@Messages("milestone")"> |
|
71 |
+ @issue.milestone.title |
|
72 |
+ </a> |
|
73 |
+ </span> |
|
74 |
+ } |
|
75 |
+ |
|
68 | 76 |
@if(issue.comments.size()>0 || issue.voters.size()>0) { |
69 | 77 |
<span class="infos-item item-count-groups"> |
70 | 78 |
@if(issue.comments.size()>0){ |
... | ... | @@ -92,15 +100,16 @@ |
92 | 100 |
<div class="empty-avatar-wrap"> </div> |
93 | 101 |
} |
94 | 102 |
</div> |
95 |
- <div class="pull-right"> |
|
96 |
- @if(project.menuSetting.milestone && issue.milestone != null) { |
|
97 |
- <div class="mileston-tag"> |
|
98 |
- <a href="@routes.MilestoneApp.milestone(project.owner, project.name, issue.milestone.id)" data-toggle="tooltip" data-placement="top" title="@Messages("milestone")"> |
|
99 |
- @issue.milestone.title |
|
100 |
- </a> |
|
101 |
- </div> |
|
103 |
+ @if(issue.dueDate != null) { |
|
104 |
+ <div class="mr20 mt10 pull-right @if(issue.isOverDueDate) {overdue}" data-toggle="tooltip" data-placement="top" title="@issue.getDueDateString"> |
|
105 |
+ <i class="yobicon-clock2"></i> |
|
106 |
+ @if(issue.isOverDueDate) { |
|
107 |
+ @Messages("issue.dueDate.overdue") |
|
108 |
+ } else { |
|
109 |
+ @issue.until |
|
102 | 110 |
} |
103 | 111 |
</div> |
112 |
+ } |
|
104 | 113 |
</div> |
105 | 114 |
</li> |
106 | 115 |
} |
--- app/views/issue/partial_search.scala.html
+++ app/views/issue/partial_search.scala.html
... | ... | @@ -177,6 +177,14 @@ |
177 | 177 |
</dd> |
178 | 178 |
</dl> |
179 | 179 |
} |
180 |
+ |
|
181 |
+ <dl class="issue-option"> |
|
182 |
+ <dt>@Messages("issue.dueDate")</dt> |
|
183 |
+ <dd class="search search-bar"> |
|
184 |
+ <input type="text" id="issueDueDate" data-toggle="calendar" name="dueDate" class="textbox full" value="@param.getDueDateString"> |
|
185 |
+ <button type="button" class="search-btn"><i class="yobicon-calendar2"></i></button> |
|
186 |
+ </dd> |
|
187 |
+ </dl> |
|
180 | 188 |
</div> |
181 | 189 |
|
182 | 190 |
<hr> |
... | ... | @@ -216,6 +224,7 @@ |
216 | 224 |
|
217 | 225 |
@if(currentPage.getList.size() > 1){ |
218 | 226 |
<div class="filters pull-right"> |
227 |
+ @makeFilterLink("dueDate", param.orderBy, param.orderDir, Messages("common.order.dueDate")) |
|
219 | 228 |
@makeFilterLink("updatedDate", param.orderBy, param.orderDir, Messages("common.order.updatedDate")) |
220 | 229 |
@makeFilterLink("createdDate", param.orderBy, param.orderDir, Messages("common.order.date")) |
221 | 230 |
@makeFilterLink("numOfComments", param.orderBy, param.orderDir, Messages("common.order.comments")) |
... | ... | @@ -243,7 +252,7 @@ |
243 | 252 |
<script type="text/javascript"> |
244 | 253 |
$(document).ready(function(){ |
245 | 254 |
// Label |
246 |
- yobi.Label.init({ |
|
255 |
+ yobi.Label.init({ |
|
247 | 256 |
"bRefresh" : true, |
248 | 257 |
"sURLLabels" : "@routes.IssueLabelApp.labels(project.owner, project.name)", |
249 | 258 |
"sURLPost" : "@routes.IssueLabelApp.newLabel(project.owner, project.name)", |
... | ... | @@ -252,12 +261,13 @@ |
252 | 261 |
|
253 | 262 |
// issue.List |
254 | 263 |
$yobi.loadModule("issue.List", { |
255 |
- "welSearchOrder": $("a[orderBy]"), |
|
256 |
- "welSearchState": $("a[state]"), |
|
257 |
- "welSearchForm" : $("form[name='search']"), |
|
258 |
- "welFilter" : $("a[pjax-filter]"), |
|
259 |
- "elPagination" : $("#pagination"), |
|
260 |
- "nTotalPages" : @currentPage.getTotalPageCount |
|
264 |
+ "welSearchOrder" : $("a[orderBy]"), |
|
265 |
+ "welSearchState" : $("a[state]"), |
|
266 |
+ "welSearchForm" : $("form[name='search']"), |
|
267 |
+ "welFilter" : $("a[pjax-filter]"), |
|
268 |
+ "elPagination" : $("#pagination"), |
|
269 |
+ "nTotalPages" : @currentPage.getTotalPageCount, |
|
270 |
+ "welIssueDueDate" : $("#issueDueDate") |
|
261 | 271 |
}); |
262 | 272 |
|
263 | 273 |
// ShortcutKey |
--- app/views/issue/view.scala.html
+++ app/views/issue/view.scala.html
... | ... | @@ -164,7 +164,11 @@ |
164 | 164 |
<dt>@Messages("milestone")</dt> |
165 | 165 |
<dd style="padding:5px 10px;"> |
166 | 166 |
@if(Milestone.findByProjectId(project.id).isEmpty()){ |
167 |
+ @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) { |
|
167 | 168 |
<a href="@routes.MilestoneApp.newMilestoneForm(project.owner, project.name)" target="_blank" class="ybtn ybtn-success ybtn-small">@Messages("milestone.menu.new")</a> |
169 |
+ } else { |
|
170 |
+ @Messages("issue.noMilestone") |
|
171 |
+ } |
|
168 | 172 |
} else { |
169 | 173 |
@defining(issue.milestone != null) { hasMilestone => |
170 | 174 |
@if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) { |
... | ... | @@ -208,6 +212,35 @@ |
208 | 212 |
</dl> |
209 | 213 |
} |
210 | 214 |
@**<!-- // -->**@ |
215 |
+ |
|
216 |
+ <dl class="issue-option"> |
|
217 |
+ <dt> |
|
218 |
+ @Messages("issue.dueDate") |
|
219 |
+ @if(issue.dueDate != null) { |
|
220 |
+ <span class="@if(issue.isOverDueDate) {overdue}"> |
|
221 |
+ @if(issue.isOverDueDate) { |
|
222 |
+ (@Messages("issue.dueDate.overdue")) |
|
223 |
+ } else { |
|
224 |
+ (@issue.until) |
|
225 |
+ } |
|
226 |
+ </span> |
|
227 |
+ } |
|
228 |
+ </dt> |
|
229 |
+ <dd style="padding:5px 10px;"> |
|
230 |
+ @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) { |
|
231 |
+ <div class="search search-bar"> |
|
232 |
+ <input type="text" id="issueDueDate" data-toggle="calendar" data-oDueDate="@issue.getDueDateString" name="dueDate" class="textbox full" value="@issue.getDueDateString"> |
|
233 |
+ <button type="button" class="search-btn"><i class="yobicon-calendar2"></i></button> |
|
234 |
+ </div> |
|
235 |
+ } else { |
|
236 |
+ @if(issue.dueDate != null) { |
|
237 |
+ @issue.getDueDateString |
|
238 |
+ } else { |
|
239 |
+ @Messages("issue.noDuedate") |
|
240 |
+ } |
|
241 |
+ } |
|
242 |
+ </dd> |
|
243 |
+ </dl> |
|
211 | 244 |
|
212 | 245 |
@**<!-- labels -->**@ |
213 | 246 |
@if(!IssueLabel.findByProject(project).isEmpty){ |
... | ... | @@ -343,6 +376,7 @@ |
343 | 376 |
@common.markdown(project) |
344 | 377 |
@common.commentDeleteModal() |
345 | 378 |
@common.select2() |
379 |
+@common.calendar() |
|
346 | 380 |
|
347 | 381 |
<link rel="stylesheet" type="text/css" media="screen" href="@routes.IssueLabelApp.labelStyles(project.owner, project.name)"> |
348 | 382 |
<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")"> |
... | ... | @@ -363,7 +397,8 @@ |
363 | 397 |
"sIssueCheckBoxesSelector": "[type=checkbox][name=checked-issue]", |
364 | 398 |
"sNextState" : "@issue.nextState().toString.toLowerCase", |
365 | 399 |
"sNextStateUrl" : "@routes.IssueApp.nextState(project.owner, project.name, issue.getNumber)", |
366 |
- "sCommentWithStateUrl": "@routes.IssueApp.newComment(project.owner, project.name, issue.getNumber)" |
|
400 |
+ "sCommentWithStateUrl": "@routes.IssueApp.newComment(project.owner, project.name, issue.getNumber)", |
|
401 |
+ "welDueDate": $("#issueDueDate") |
|
367 | 402 |
}); |
368 | 403 |
|
369 | 404 |
// yobi.ShortcutKey |
--- app/views/layout.scala.html
+++ app/views/layout.scala.html
... | ... | @@ -31,6 +31,7 @@ |
31 | 31 |
<link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.at("bootstrap/css/bootstrap.css")"> |
32 | 32 |
<link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.at("stylesheets/yobicon/style.css")"> |
33 | 33 |
<link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.at("javascripts/lib/select2/select2.css")"/> |
34 |
+<link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.at("javascripts/lib/pikaday/pikaday.css")" /> |
|
34 | 35 |
<link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.at("stylesheets/yobi.css")"> |
35 | 36 |
|
36 | 37 |
<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery/jquery-1.9.0.js")"></script> |
--- app/views/user/partial_issues.scala.html
+++ app/views/user/partial_issues.scala.html
... | ... | @@ -30,7 +30,7 @@ |
30 | 30 |
} |
31 | 31 |
@defining(User.findByLoginId(issue.authorLoginId)){ user => |
32 | 32 |
<li class="post-item " id="issue-item-@issue.id"> |
33 |
- <div class="span9"> |
|
33 |
+ <div class="span10"> |
|
34 | 34 |
<a href="@routes.ProjectApp.project(project.owner, project.name)" class="avatar-wrap mlarge"> |
35 | 35 |
<img src="@urlToProjectLogo(project)" alt="@project.owner / @project.name"> |
36 | 36 |
</a> |
... | ... | @@ -56,6 +56,14 @@ |
56 | 56 |
@agoOrDateString(issue.createdDate) |
57 | 57 |
</span> |
58 | 58 |
|
59 |
+ @if(project.menuSetting.milestone && issue.milestone != null) { |
|
60 |
+ <span class="infos-item mileston-tag"> |
|
61 |
+ <a href="@routes.MilestoneApp.milestone(issue.project.owner, issue.project.name, issue.milestone.id)" data-toggle="tooltip" data-placement="top" title="@Messages("milestone")"> |
|
62 |
+ @issue.milestone.title |
|
63 |
+ </a> |
|
64 |
+ </span> |
|
65 |
+ } |
|
66 |
+ |
|
59 | 67 |
@if(issue.comments.size()>0 || issue.voters.size()>0) { |
60 | 68 |
<span class="infos-item item-count-groups"> |
61 | 69 |
@if(issue.comments.size()>0){ |
... | ... | @@ -72,7 +80,7 @@ |
72 | 80 |
} |
73 | 81 |
</div> |
74 | 82 |
</div> |
75 |
- <div class="span3"> |
|
83 |
+ <div class="span2"> |
|
76 | 84 |
<div class="mt5 pull-right"> |
77 | 85 |
@if(issue.assigneeName != null) { |
78 | 86 |
<a href="@routes.UserApp.userInfo(issue.assignee.user.loginId)" class="avatar-wrap assinee" data-toggle="tooltip" data-placement="top" title="@Messages("issue.assignee"): @issue.assigneeName"> |
... | ... | @@ -83,14 +91,17 @@ |
83 | 91 |
} |
84 | 92 |
</div> |
85 | 93 |
<div class="pull-right"> |
86 |
- @if(project.menuSetting.milestone && issue.milestone != null) { |
|
87 |
- <div class="mileston-tag"> |
|
88 |
- <a href="@routes.MilestoneApp.milestone(issue.project.owner, issue.project.name, issue.milestone.id)" data-toggle="tooltip" data-placement="top" title="@Messages("milestone")"> |
|
89 |
- @issue.milestone.title |
|
90 |
- </a> |
|
91 |
- </div> |
|
92 |
- } |
|
94 |
+ @if(issue.dueDate != null) { |
|
95 |
+ <div class="mr20 mt10 pull-right @if(issue.isOverDueDate) {overdue}" data-toggle="tooltip" data-placement="top" title="@issue.getDueDateString"> |
|
96 |
+ <i class="yobicon-clock2"></i> |
|
97 |
+ @if(issue.isOverDueDate) { |
|
98 |
+ @Messages("issue.dueDate.overdue") |
|
99 |
+ } else { |
|
100 |
+ @issue.until |
|
101 |
+ } |
|
102 |
+ </div> |
|
93 | 103 |
</div> |
104 |
+ } |
|
94 | 105 |
</div> |
95 | 106 |
</li> |
96 | 107 |
} |
+++ conf/evolutions/default/85.sql
... | ... | @@ -0,0 +1,6 @@ |
1 | +# --- !Ups | |
2 | + | |
3 | +ALTER TABLE issue add column due_date timestamp; | |
4 | + | |
5 | +# --- !Downs | |
6 | +ALTER TABLE issue drop column due_date; |
--- conf/messages
+++ conf/messages
... | ... | @@ -142,17 +142,18 @@ |
142 | 142 |
common.none = None |
143 | 143 |
common.noAuthor = No author |
144 | 144 |
common.order.all = All |
145 |
-common.order.comments = Order by comments |
|
146 |
-common.order.completionRate = Order by completion Rate |
|
147 |
-common.order.date = Order by date |
|
148 |
-common.order.dueDate = Order by due date |
|
149 |
-common.order.name = Order by name |
|
150 |
-common.order.recent = Order by creation date |
|
151 |
-common.order.updatedDate = Order by updated date |
|
145 |
+common.order.comments = comments |
|
146 |
+common.order.completionRate = completion Rate |
|
147 |
+common.order.date = created date |
|
148 |
+common.order.dueDate = due date |
|
149 |
+common.order.name = name |
|
150 |
+common.order.recent = creation date |
|
151 |
+common.order.updatedDate = updated date |
|
152 | 152 |
common.time.after = After {0} days |
153 | 153 |
common.time.before = Before {0} days |
154 | 154 |
common.time.day = {0} days ago |
155 | 155 |
common.time.days = {0} days ago |
156 |
+common.time.default.day = {0} days |
|
156 | 157 |
common.time.hour = {0} hours ago |
157 | 158 |
common.time.hours = {0} hours ago |
158 | 159 |
common.time.just = Just now |
... | ... | @@ -217,6 +218,8 @@ |
217 | 218 |
issue.createdDate = Created date |
218 | 219 |
issue.delete = Delete issue |
219 | 220 |
issue.downloadAsExcel = Download as Excel file |
221 |
+issue.dueDate = Due date |
|
222 |
+issue.dueDate.overdue = Overdue |
|
220 | 223 |
issue.error.beforeunload = Issue has not saved yet. Would you like to exit this page without saving? |
221 | 224 |
issue.error.emptyBody = Issue description is a required field. |
222 | 225 |
issue.error.emptyTitle = Issue title is a required field. |
... | ... | @@ -238,6 +241,7 @@ |
238 | 241 |
issue.noAssignee = No assignee |
239 | 242 |
issue.noAuthor = No author |
240 | 243 |
issue.noMilestone = No milestone |
244 |
+issue.noDuedate = No due date |
|
241 | 245 |
issue.search = SEARCH |
242 | 246 |
issue.state = Status |
243 | 247 |
issue.state.all = All |
... | ... | @@ -250,6 +254,7 @@ |
250 | 254 |
issue.update.assignee = Update assignee |
251 | 255 |
issue.update.attachLabel = Attach label |
252 | 256 |
issue.update.detachLabel = Detach label |
257 |
+issue.update.duedate = Update due date |
|
253 | 258 |
issue.update.label = Update label |
254 | 259 |
issue.update.milestone = Update milestone |
255 | 260 |
issue.update.state = Update status |
--- conf/messages.ko
+++ conf/messages.ko
... | ... | @@ -153,6 +153,7 @@ |
153 | 153 |
common.time.before = {0}일 전 |
154 | 154 |
common.time.day = {0}일 전 |
155 | 155 |
common.time.days = {0}일 전 |
156 |
+common.time.default.day = {0}일 |
|
156 | 157 |
common.time.hour = {0}시간 전 |
157 | 158 |
common.time.hours = {0}시간 전 |
158 | 159 |
common.time.just = 방금 전 |
... | ... | @@ -217,6 +218,8 @@ |
217 | 218 |
issue.createdDate = 작성일 |
218 | 219 |
issue.delete = 이슈 삭제 |
219 | 220 |
issue.downloadAsExcel = 엑셀파일로 다운받기 |
221 |
+issue.dueDate= 목표 완료일 |
|
222 |
+issue.dueDate.overdue= 기한지남 |
|
220 | 223 |
issue.error.beforeunload = 아직 이슈를 저장하지 않았습니다. 저장하지 않은 채로 다른 페이지로 이동하시겠습니까? |
221 | 224 |
issue.error.emptyBody = 이슈 내용을 입력해주세요 |
222 | 225 |
issue.error.emptyTitle = 이슈 제목을 입력해주세요 |
... | ... | @@ -238,6 +241,7 @@ |
238 | 241 |
issue.noAssignee = 담당자 없음 |
239 | 242 |
issue.noAuthor = 작성자 없음 |
240 | 243 |
issue.noMilestone = 마일스톤 없음 |
244 |
+issue.noDuedate = 목표 완료일 없음 |
|
241 | 245 |
issue.search = 검색 |
242 | 246 |
issue.state = 상태 |
243 | 247 |
issue.state.all = 전체 |
... | ... | @@ -250,6 +254,7 @@ |
250 | 254 |
issue.update.assignee = 담당자 변경 |
251 | 255 |
issue.update.attachLabel = 라벨 추가 |
252 | 256 |
issue.update.detachLabel = 라벨 제거 |
257 |
+issue.update.duedate = 목표완료일 변경 |
|
253 | 258 |
issue.update.label = 라벨 변경 |
254 | 259 |
issue.update.milestone = 마일스톤 변경 |
255 | 260 |
issue.update.state = 상태 변경 |
+++ public/javascripts/common/yobi.ui.Calendar.js
... | ... | @@ -0,0 +1,87 @@ |
1 | +/** | |
2 | + * Yobi, Project Hosting SW | |
3 | + * | |
4 | + * Copyright 2014 NAVER Corp. | |
5 | + * http://yobi.io | |
6 | + * | |
7 | + * @Author Changgun Kim | |
8 | + * | |
9 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
10 | + * you may not use this file except in compliance with the License. | |
11 | + * You may obtain a copy of the License at | |
12 | + * | |
13 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
14 | + * | |
15 | + * Unless required by applicable law or agreed to in writing, software | |
16 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
17 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | + * See the License for the specific language governing permissions and | |
19 | + * limitations under the License. | |
20 | + */ | |
21 | +/** | |
22 | + * yobi.ui.Calendar | |
23 | + * | |
24 | + * @requires Pikaday.js (https://github.com/dbushell/Pikaday/) | |
25 | + */ | |
26 | + | |
27 | +(function(ns){ | |
28 | + var oNS = $yobi.createNamespace(ns); | |
29 | + oNS.container[oNS.name] = function(elInput, htOptions){ | |
30 | + var welInput = $(elInput); | |
31 | + | |
32 | + var htVar = { | |
33 | + "sDateFormat": "YYYY-MM-DD", | |
34 | + "rxDateFormat": /\d{4}-\d{2}-\d{2}/ | |
35 | + }; | |
36 | + | |
37 | + var htElement = {}; | |
38 | + | |
39 | + function _init(htOptions){ | |
40 | + _initElement(htOptions); | |
41 | + } | |
42 | + | |
43 | + function _initElement(htOptions) { | |
44 | + if (!welInput.data("pickaday")) { | |
45 | + htVar.oPicker = new Pikaday({ | |
46 | + "format": htVar.sDateFormat, | |
47 | + "field": welInput.get(0), | |
48 | + "setDefaultDate": true, | |
49 | + "defaultDate": welInput.val() | |
50 | + }); | |
51 | + welInput.data("pickaday", true); | |
52 | + | |
53 | + htElement.welBtn = welInput.next(); | |
54 | + | |
55 | + htElement.welBtn.on("click", function() { | |
56 | + htVar.oPicker.show(); | |
57 | + }); | |
58 | + | |
59 | + if(welInput.val().length > 0){ | |
60 | + htVar.oPicker.setDate(welInput.val()); | |
61 | + } | |
62 | + } | |
63 | + } | |
64 | + | |
65 | + function _getDate() { | |
66 | + return htVar.oPicker.getDate(); | |
67 | + } | |
68 | + | |
69 | + function _setDate(dateStr) { | |
70 | + return htVar.oPicker.setDate(dateStr); | |
71 | + } | |
72 | + | |
73 | + _init(htOptions); | |
74 | + | |
75 | + return { | |
76 | + "getDate": _getDate, | |
77 | + "setDate": _setDate | |
78 | + }; | |
79 | + }; | |
80 | + | |
81 | +})("yobi.ui.Calendar"); | |
82 | + | |
83 | +$(function(){ | |
84 | + $('[data-toggle="calendar"]').each(function(i, el){ | |
85 | + yobi.ui.Calendar(el); | |
86 | + }); | |
87 | +}); |
--- public/javascripts/lib/pikaday/pikaday.js
+++ public/javascripts/lib/pikaday/pikaday.js
... | ... | @@ -1,450 +1,564 @@ |
1 | 1 |
/*! |
2 | 2 |
* Pikaday |
3 |
- * Copyright © 2012 David Bushell | BSD & MIT license | http://dbushell.com/ |
|
3 |
+ * |
|
4 |
+ * Copyright © 2014 David Bushell | BSD & MIT license | https://github.com/dbushell/Pikaday |
|
4 | 5 |
*/ |
5 | 6 |
|
6 |
-(function(window, document, undefined) |
|
7 |
+(function (root, factory) |
|
8 |
+{ |
|
9 |
+ 'use strict'; |
|
10 |
+ |
|
11 |
+ var moment; |
|
12 |
+ if (typeof exports === 'object') { |
|
13 |
+ // CommonJS module |
|
14 |
+ // Load moment.js as an optional dependency |
|
15 |
+ try { moment = require('moment'); } catch (e) {} |
|
16 |
+ module.exports = factory(moment); |
|
17 |
+ } else if (typeof define === 'function' && define.amd) { |
|
18 |
+ // AMD. Register as an anonymous module. |
|
19 |
+ define(function (req) |
|
20 |
+ { |
|
21 |
+ // Load moment.js as an optional dependency |
|
22 |
+ var id = 'moment'; |
|
23 |
+ moment = req.defined && req.defined(id) ? req(id) : undefined; |
|
24 |
+ return factory(moment); |
|
25 |
+ }); |
|
26 |
+ } else { |
|
27 |
+ root.Pikaday = factory(root.moment); |
|
28 |
+ } |
|
29 |
+}(this, function (moment) |
|
7 | 30 |
{ |
8 | 31 |
'use strict'; |
9 | 32 |
|
10 | 33 |
/** |
11 | 34 |
* feature detection and helper functions |
12 | 35 |
*/ |
13 |
- var hasMoment = typeof window.moment === 'function', |
|
36 |
+ var hasMoment = typeof moment === 'function', |
|
14 | 37 |
|
15 |
- hasEventListeners = !!window.addEventListener, |
|
38 |
+ hasEventListeners = !!window.addEventListener, |
|
16 | 39 |
|
17 |
- sto = window.setTimeout, |
|
40 |
+ document = window.document, |
|
18 | 41 |
|
19 |
- addEvent = function(el, e, callback, capture) |
|
20 |
- { |
|
21 |
- if (hasEventListeners) { |
|
22 |
- el.addEventListener(e, callback, !!capture); |
|
23 |
- } else { |
|
24 |
- el.attachEvent('on' + e, callback); |
|
25 |
- } |
|
26 |
- }, |
|
42 |
+ sto = window.setTimeout, |
|
27 | 43 |
|
28 |
- removeEvent = function(el, e, callback, capture) |
|
29 |
- { |
|
30 |
- if (hasEventListeners) { |
|
31 |
- el.removeEventListener(e, callback, !!capture); |
|
32 |
- } else { |
|
33 |
- el.detachEvent('on' + e, callback); |
|
34 |
- } |
|
35 |
- }, |
|
36 |
- |
|
37 |
- trim = function(str) |
|
38 |
- { |
|
39 |
- return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,''); |
|
40 |
- }, |
|
41 |
- |
|
42 |
- hasClass = function(el, cn) |
|
43 |
- { |
|
44 |
- return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1; |
|
45 |
- }, |
|
46 |
- |
|
47 |
- addClass = function(el, cn) |
|
48 |
- { |
|
49 |
- if (!hasClass(el, cn)) { |
|
50 |
- el.className = (el.className === '') ? cn : el.className + ' ' + cn; |
|
51 |
- } |
|
52 |
- }, |
|
53 |
- |
|
54 |
- removeClass = function(el, cn) |
|
55 |
- { |
|
56 |
- el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' ')); |
|
57 |
- }, |
|
58 |
- |
|
59 |
- isArray = function(obj) |
|
60 |
- { |
|
61 |
- return (/Array/).test(Object.prototype.toString.call(obj)); |
|
62 |
- }, |
|
63 |
- |
|
64 |
- isDate = function(obj) |
|
65 |
- { |
|
66 |
- return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime()); |
|
67 |
- }, |
|
68 |
- |
|
69 |
- isLeapYear = function(year) |
|
70 |
- { |
|
71 |
- // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951 |
|
72 |
- return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; |
|
73 |
- }, |
|
74 |
- |
|
75 |
- getDaysInMonth = function(year, month) |
|
76 |
- { |
|
77 |
- return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; |
|
78 |
- }, |
|
79 |
- |
|
80 |
- compareDates = function(a,b) |
|
81 |
- { |
|
82 |
- // weak date comparison (use date.setHours(0,0,0,0) to ensure correct result) |
|
83 |
- return a.getTime() === b.getTime(); |
|
84 |
- }, |
|
85 |
- |
|
86 |
- extend = function(to, from, overwrite) |
|
87 |
- { |
|
88 |
- var prop, hasProp; |
|
89 |
- for (prop in from) { |
|
90 |
- hasProp = to[prop] !== undefined; |
|
91 |
- if (hasProp && typeof from[prop] === 'object' && from[prop].nodeName === undefined) { |
|
92 |
- if (isDate(from[prop])) { |
|
93 |
- if (overwrite) { |
|
94 |
- to[prop] = new Date(from[prop].getTime()); |
|
95 |
- } |
|
96 |
- } |
|
97 |
- else if (isArray(from[prop])) { |
|
98 |
- if (overwrite) { |
|
99 |
- to[prop] = from[prop].slice(0); |
|
100 |
- } |
|
101 |
- } else { |
|
102 |
- to[prop] = extend({}, from[prop], overwrite); |
|
103 |
- } |
|
104 |
- } else if (overwrite || !hasProp) { |
|
105 |
- to[prop] = from[prop]; |
|
44 |
+ addEvent = function(el, e, callback, capture) |
|
45 |
+ { |
|
46 |
+ if (hasEventListeners) { |
|
47 |
+ el.addEventListener(e, callback, !!capture); |
|
48 |
+ } else { |
|
49 |
+ el.attachEvent('on' + e, callback); |
|
106 | 50 |
} |
107 |
- } |
|
108 |
- return to; |
|
109 |
- }, |
|
110 |
- |
|
111 |
- |
|
112 |
- /** |
|
113 |
- * defaults and localisation |
|
114 |
- */ |
|
115 |
- defaults = { |
|
116 |
- |
|
117 |
- // bind the picker to a form field |
|
118 |
- field: null, |
|
119 |
- |
|
120 |
- // automatically show/hide the picker on `field` focus (default `true` if `field` is set) |
|
121 |
- bound: undefined, |
|
122 |
- |
|
123 |
- // the default output format for `.toString()` and `field` value |
|
124 |
- format: 'YYYY-MM-DD', |
|
125 |
- |
|
126 |
- // the initial date to view when first opened |
|
127 |
- defaultDate: null, |
|
128 |
- |
|
129 |
- // make the `defaultDate` the initial selected value |
|
130 |
- setDefaultDate: false, |
|
131 |
- |
|
132 |
- // first day of week (0: Sunday, 1: Monday etc) |
|
133 |
- firstDay: 0, |
|
134 |
- |
|
135 |
- // the minimum/earliest date that can be selected |
|
136 |
- minDate: null, |
|
137 |
- // the maximum/latest date that can be selected |
|
138 |
- maxDate: null, |
|
139 |
- |
|
140 |
- // number of years either side, or array of upper/lower range |
|
141 |
- yearRange: 10, |
|
142 |
- |
|
143 |
- // used internally (don't config outside) |
|
144 |
- minYear: 0, |
|
145 |
- maxYear: 9999, |
|
146 |
- minMonth: undefined, |
|
147 |
- maxMonth: undefined, |
|
148 |
- |
|
149 |
- isRTL: false, |
|
150 |
- |
|
151 |
- // how many months are visible (not implemented yet) |
|
152 |
- numberOfMonths: 1, |
|
153 |
- |
|
154 |
- // internationalization |
|
155 |
- i18n: { |
|
156 |
- |
|
157 |
- months : ['January','February','March','April','May','June','July','August','September','October','November','December'], |
|
158 |
- //monthsShort : ['Jan_Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'], |
|
159 |
- weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], |
|
160 |
- weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'] |
|
161 | 51 |
}, |
162 | 52 |
|
163 |
- // callback function |
|
164 |
- onSelect: null, |
|
165 |
- onOpen: null, |
|
166 |
- onClose: null |
|
167 |
- }, |
|
168 |
- |
|
169 |
- |
|
170 |
- /** |
|
171 |
- * templating functions to abstract HTML rendering |
|
172 |
- */ |
|
173 |
- renderDayName = function(opts, day, abbr) |
|
174 |
- { |
|
175 |
- day += opts.firstDay; |
|
176 |
- while (day >= 7) { |
|
177 |
- day -= 7; |
|
178 |
- } |
|
179 |
- return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day]; |
|
180 |
- }, |
|
181 |
- |
|
182 |
- renderDay = function(i, isSelected, isToday, isDisabled, isEmpty) |
|
183 |
- { |
|
184 |
- if (isEmpty) { |
|
185 |
- return '<td class="is-empty"></td>'; |
|
186 |
- } |
|
187 |
- var arr = []; |
|
188 |
- if (isDisabled) { |
|
189 |
- arr.push('is-disabled'); |
|
190 |
- } |
|
191 |
- if (isToday) { |
|
192 |
- arr.push('is-today'); |
|
193 |
- } |
|
194 |
- if (isSelected) { |
|
195 |
- arr.push('is-selected'); |
|
196 |
- } |
|
197 |
- return '<td data-day="' + i + '" class="' + arr.join(' ') + '"><button class="pika-button" type="button">' + i + '</button>' + '</td>'; |
|
198 |
- }, |
|
199 |
- |
|
200 |
- renderRow = function(days, isRTL) |
|
201 |
- { |
|
202 |
- return '<tr>' + (isRTL ? days.reverse() : days).join('') + '</tr>'; |
|
203 |
- }, |
|
204 |
- |
|
205 |
- renderBody = function(rows) |
|
206 |
- { |
|
207 |
- return '<tbody>' + rows.join('') + '</tbody>'; |
|
208 |
- }, |
|
209 |
- |
|
210 |
- renderHead = function(opts) |
|
211 |
- { |
|
212 |
- var i, arr = []; |
|
213 |
- for (i = 0; i < 7; i++) { |
|
214 |
- arr.push('<th scope="col"><abbr title="' + renderDayName(opts, i) + '">' + renderDayName(opts, i, true) + '</abbr></th>'); |
|
215 |
- } |
|
216 |
- return '<thead>' + (opts.isRTL ? arr.reverse() : arr).join('') + '</thead>'; |
|
217 |
- }, |
|
218 |
- |
|
219 |
- renderTitle = function(instance) |
|
220 |
- { |
|
221 |
- var i, j, arr, |
|
222 |
- opts = instance._o, |
|
223 |
- month = instance._m, |
|
224 |
- year = instance._y, |
|
225 |
- isMinYear = year === opts.minYear, |
|
226 |
- isMaxYear = year === opts.maxYear, |
|
227 |
- html = '<div class="pika-title">', |
|
228 |
- prev = true, |
|
229 |
- next = true; |
|
230 |
- |
|
231 |
- for (arr = [], i = 0; i < 12; i++) { |
|
232 |
- arr.push('<option value="' + i + '"' + |
|
233 |
- (i === month ? ' selected': '') + |
|
234 |
- ((isMinYear && i < opts.minMonth) || (isMaxYear && i > opts.maxMonth) ? 'disabled' : '') + '>' + |
|
235 |
- opts.i18n.months[i] + '</option>'); |
|
236 |
- } |
|
237 |
- html += '<div class="pika-label">' + opts.i18n.months[month] + '<select class="pika-select pika-select-month">' + arr.join('') + '</select></div>'; |
|
238 |
- |
|
239 |
- if (isArray(opts.yearRange)) { |
|
240 |
- i = opts.yearRange[0]; |
|
241 |
- j = opts.yearRange[1] + 1; |
|
242 |
- } else { |
|
243 |
- i = year - opts.yearRange; |
|
244 |
- j = 1 + year + opts.yearRange; |
|
245 |
- } |
|
246 |
- |
|
247 |
- for (arr = []; i < j && i <= opts.maxYear; i++) { |
|
248 |
- if (i >= opts.minYear) { |
|
249 |
- arr.push('<option value="' + i + '"' + (i === year ? ' selected': '') + '>' + (i) + '</option>'); |
|
250 |
- } |
|
251 |
- } |
|
252 |
- html += '<div class="pika-label">' + year + '<select class="pika-select pika-select-year">' + arr.join('') + '</select></div>'; |
|
253 |
- |
|
254 |
- if (isMinYear && (month === 0 || opts.minMonth >= month)) { |
|
255 |
- prev = false; |
|
256 |
- } |
|
257 |
- |
|
258 |
- if (isMaxYear && (month === 11 || opts.maxMonth <= month)) { |
|
259 |
- next = false; |
|
260 |
- } |
|
261 |
- |
|
262 |
- html += '<button class="pika-prev' + (prev ? '' : ' is-disabled') + '" type="button">Previous Month</button>'; |
|
263 |
- html += '<button class="pika-next' + (next ? '' : ' is-disabled') + '" type="button">Next Month</button>'; |
|
264 |
- |
|
265 |
- return html += '</div>'; |
|
266 |
- }, |
|
267 |
- |
|
268 |
- renderTable = function(opts, data) |
|
269 |
- { |
|
270 |
- return '<table cellpadding="0" cellspacing="0" class="pika-table">' + renderHead(opts) + renderBody(data) + '</table>'; |
|
271 |
- }; |
|
272 |
- |
|
273 |
- |
|
274 |
- /** |
|
275 |
- * Pikaday constructor |
|
276 |
- */ |
|
277 |
- window.Pikaday = function(options) |
|
278 |
- { |
|
279 |
- var self = this, |
|
280 |
- opts = self.config(options); |
|
281 |
- |
|
282 |
- self._onMouseDown = function(e) |
|
53 |
+ removeEvent = function(el, e, callback, capture) |
|
283 | 54 |
{ |
284 |
- if (!self._v) { |
|
285 |
- return; |
|
55 |
+ if (hasEventListeners) { |
|
56 |
+ el.removeEventListener(e, callback, !!capture); |
|
57 |
+ } else { |
|
58 |
+ el.detachEvent('on' + e, callback); |
|
286 | 59 |
} |
287 |
- e = e || window.event; |
|
288 |
- var target = e.target || e.srcElement; |
|
289 |
- if (!target) { |
|
290 |
- return; |
|
291 |
- } |
|
60 |
+ }, |
|
292 | 61 |
|
293 |
- if (!hasClass(target, 'is-disabled')) { |
|
294 |
- if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty')) { |
|
295 |
- self.setDate(new Date(self._y, self._m, parseInt(target.innerHTML, 10))); |
|
296 |
- if (opts.bound) { |
|
297 |
- sto(function() { |
|
298 |
- self.hide(); |
|
299 |
- }, 100); |
|
62 |
+ fireEvent = function(el, eventName, data) |
|
63 |
+ { |
|
64 |
+ var ev; |
|
65 |
+ |
|
66 |
+ if (document.createEvent) { |
|
67 |
+ ev = document.createEvent('HTMLEvents'); |
|
68 |
+ ev.initEvent(eventName, true, false); |
|
69 |
+ ev = extend(ev, data); |
|
70 |
+ el.dispatchEvent(ev); |
|
71 |
+ } else if (document.createEventObject) { |
|
72 |
+ ev = document.createEventObject(); |
|
73 |
+ ev = extend(ev, data); |
|
74 |
+ el.fireEvent('on' + eventName, ev); |
|
75 |
+ } |
|
76 |
+ }, |
|
77 |
+ |
|
78 |
+ trim = function(str) |
|
79 |
+ { |
|
80 |
+ return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,''); |
|
81 |
+ }, |
|
82 |
+ |
|
83 |
+ hasClass = function(el, cn) |
|
84 |
+ { |
|
85 |
+ return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1; |
|
86 |
+ }, |
|
87 |
+ |
|
88 |
+ addClass = function(el, cn) |
|
89 |
+ { |
|
90 |
+ if (!hasClass(el, cn)) { |
|
91 |
+ el.className = (el.className === '') ? cn : el.className + ' ' + cn; |
|
92 |
+ } |
|
93 |
+ }, |
|
94 |
+ |
|
95 |
+ removeClass = function(el, cn) |
|
96 |
+ { |
|
97 |
+ el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' ')); |
|
98 |
+ }, |
|
99 |
+ |
|
100 |
+ isArray = function(obj) |
|
101 |
+ { |
|
102 |
+ return (/Array/).test(Object.prototype.toString.call(obj)); |
|
103 |
+ }, |
|
104 |
+ |
|
105 |
+ isDate = function(obj) |
|
106 |
+ { |
|
107 |
+ return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime()); |
|
108 |
+ }, |
|
109 |
+ |
|
110 |
+ isLeapYear = function(year) |
|
111 |
+ { |
|
112 |
+ // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951 |
|
113 |
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; |
|
114 |
+ }, |
|
115 |
+ |
|
116 |
+ getDaysInMonth = function(year, month) |
|
117 |
+ { |
|
118 |
+ return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; |
|
119 |
+ }, |
|
120 |
+ |
|
121 |
+ setToStartOfDay = function(date) |
|
122 |
+ { |
|
123 |
+ if (isDate(date)) date.setHours(0,0,0,0); |
|
124 |
+ }, |
|
125 |
+ |
|
126 |
+ compareDates = function(a,b) |
|
127 |
+ { |
|
128 |
+ // weak date comparison (use setToStartOfDay(date) to ensure correct result) |
|
129 |
+ return a.getTime() === b.getTime(); |
|
130 |
+ }, |
|
131 |
+ |
|
132 |
+ extend = function(to, from, overwrite) |
|
133 |
+ { |
|
134 |
+ var prop, hasProp; |
|
135 |
+ for (prop in from) { |
|
136 |
+ hasProp = to[prop] !== undefined; |
|
137 |
+ if (hasProp && typeof from[prop] === 'object' && from[prop].nodeName === undefined) { |
|
138 |
+ if (isDate(from[prop])) { |
|
139 |
+ if (overwrite) { |
|
140 |
+ to[prop] = new Date(from[prop].getTime()); |
|
141 |
+ } |
|
300 | 142 |
} |
143 |
+ else if (isArray(from[prop])) { |
|
144 |
+ if (overwrite) { |
|
145 |
+ to[prop] = from[prop].slice(0); |
|
146 |
+ } |
|
147 |
+ } else { |
|
148 |
+ to[prop] = extend({}, from[prop], overwrite); |
|
149 |
+ } |
|
150 |
+ } else if (overwrite || !hasProp) { |
|
151 |
+ to[prop] = from[prop]; |
|
152 |
+ } |
|
153 |
+ } |
|
154 |
+ return to; |
|
155 |
+ }, |
|
156 |
+ |
|
157 |
+ adjustCalendar = function(calendar) { |
|
158 |
+ if (calendar.month < 0) { |
|
159 |
+ calendar.year -= Math.ceil(Math.abs(calendar.month)/12); |
|
160 |
+ calendar.month += 12; |
|
161 |
+ } |
|
162 |
+ if (calendar.month > 11) { |
|
163 |
+ calendar.year += Math.floor(Math.abs(calendar.month)/12); |
|
164 |
+ calendar.month -= 12; |
|
165 |
+ } |
|
166 |
+ return calendar; |
|
167 |
+ }, |
|
168 |
+ |
|
169 |
+ /** |
|
170 |
+ * defaults and localisation |
|
171 |
+ */ |
|
172 |
+ defaults = { |
|
173 |
+ |
|
174 |
+ // bind the picker to a form field |
|
175 |
+ field: null, |
|
176 |
+ |
|
177 |
+ // automatically show/hide the picker on `field` focus (default `true` if `field` is set) |
|
178 |
+ bound: undefined, |
|
179 |
+ |
|
180 |
+ // position of the datepicker, relative to the field (default to bottom & left) |
|
181 |
+ // ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position) |
|
182 |
+ position: 'bottom left', |
|
183 |
+ |
|
184 |
+ // the default output format for `.toString()` and `field` value |
|
185 |
+ format: 'YYYY-MM-DD', |
|
186 |
+ |
|
187 |
+ // the initial date to view when first opened |
|
188 |
+ defaultDate: null, |
|
189 |
+ |
|
190 |
+ // make the `defaultDate` the initial selected value |
|
191 |
+ setDefaultDate: false, |
|
192 |
+ |
|
193 |
+ // first day of week (0: Sunday, 1: Monday etc) |
|
194 |
+ firstDay: 0, |
|
195 |
+ |
|
196 |
+ // the minimum/earliest date that can be selected |
|
197 |
+ minDate: null, |
|
198 |
+ // the maximum/latest date that can be selected |
|
199 |
+ maxDate: null, |
|
200 |
+ |
|
201 |
+ // number of years either side, or array of upper/lower range |
|
202 |
+ yearRange: 10, |
|
203 |
+ |
|
204 |
+ // show week numbers at head of row |
|
205 |
+ showWeekNumber: false, |
|
206 |
+ |
|
207 |
+ // used internally (don't config outside) |
|
208 |
+ minYear: 0, |
|
209 |
+ maxYear: 9999, |
|
210 |
+ minMonth: undefined, |
|
211 |
+ maxMonth: undefined, |
|
212 |
+ |
|
213 |
+ isRTL: false, |
|
214 |
+ |
|
215 |
+ // Additional text to append to the year in the calendar title |
|
216 |
+ yearSuffix: '', |
|
217 |
+ |
|
218 |
+ // Render the month after year in the calendar title |
|
219 |
+ showMonthAfterYear: false, |
|
220 |
+ |
|
221 |
+ // how many months are visible |
|
222 |
+ numberOfMonths: 1, |
|
223 |
+ |
|
224 |
+ // when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`) |
|
225 |
+ // only used for the first display or when a selected date is not visible |
|
226 |
+ mainCalendar: 'left', |
|
227 |
+ |
|
228 |
+ // Specify a DOM element to render the calendar in |
|
229 |
+ container: undefined, |
|
230 |
+ |
|
231 |
+ // internationalization |
|
232 |
+ i18n: { |
|
233 |
+ previousMonth : 'Previous Month', |
|
234 |
+ nextMonth : 'Next Month', |
|
235 |
+ months : ['January','February','March','April','May','June','July','August','September','October','November','December'], |
|
236 |
+ weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], |
|
237 |
+ weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'] |
|
238 |
+ }, |
|
239 |
+ |
|
240 |
+ // callback function |
|
241 |
+ onSelect: null, |
|
242 |
+ onOpen: null, |
|
243 |
+ onClose: null, |
|
244 |
+ onDraw: null |
|
245 |
+ }, |
|
246 |
+ |
|
247 |
+ |
|
248 |
+ /** |
|
249 |
+ * templating functions to abstract HTML rendering |
|
250 |
+ */ |
|
251 |
+ renderDayName = function(opts, day, abbr) |
|
252 |
+ { |
|
253 |
+ day += opts.firstDay; |
|
254 |
+ while (day >= 7) { |
|
255 |
+ day -= 7; |
|
256 |
+ } |
|
257 |
+ return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day]; |
|
258 |
+ }, |
|
259 |
+ |
|
260 |
+ renderDay = function(d, m, y, isSelected, isToday, isDisabled, isEmpty) |
|
261 |
+ { |
|
262 |
+ if (isEmpty) { |
|
263 |
+ return '<td class="is-empty"></td>'; |
|
264 |
+ } |
|
265 |
+ var arr = []; |
|
266 |
+ if (isDisabled) { |
|
267 |
+ arr.push('is-disabled'); |
|
268 |
+ } |
|
269 |
+ if (isToday) { |
|
270 |
+ arr.push('is-today'); |
|
271 |
+ } |
|
272 |
+ if (isSelected) { |
|
273 |
+ arr.push('is-selected'); |
|
274 |
+ } |
|
275 |
+ return '<td data-day="' + d + '" class="' + arr.join(' ') + '">' + |
|
276 |
+ '<button class="pika-button pika-day" type="button" ' + |
|
277 |
+ 'data-pika-year="' + y + '" data-pika-month="' + m + '" data-pika-day="' + d + '">' + |
|
278 |
+ d + |
|
279 |
+ '</button>' + |
|
280 |
+ '</td>'; |
|
281 |
+ }, |
|
282 |
+ |
|
283 |
+ renderWeek = function (d, m, y) { |
|
284 |
+ // Lifted from http://javascript.about.com/library/blweekyear.htm, lightly modified. |
|
285 |
+ var onejan = new Date(y, 0, 1), |
|
286 |
+ weekNum = Math.ceil((((new Date(y, m, d) - onejan) / 86400000) + onejan.getDay()+1)/7); |
|
287 |
+ return '<td class="pika-week">' + weekNum + '</td>'; |
|
288 |
+ }, |
|
289 |
+ |
|
290 |
+ renderRow = function(days, isRTL) |
|
291 |
+ { |
|
292 |
+ return '<tr>' + (isRTL ? days.reverse() : days).join('') + '</tr>'; |
|
293 |
+ }, |
|
294 |
+ |
|
295 |
+ renderBody = function(rows) |
|
296 |
+ { |
|
297 |
+ return '<tbody>' + rows.join('') + '</tbody>'; |
|
298 |
+ }, |
|
299 |
+ |
|
300 |
+ renderHead = function(opts) |
|
301 |
+ { |
|
302 |
+ var i, arr = []; |
|
303 |
+ if (opts.showWeekNumber) { |
|
304 |
+ arr.push('<th></th>'); |
|
305 |
+ } |
|
306 |
+ for (i = 0; i < 7; i++) { |
|
307 |
+ arr.push('<th scope="col"><abbr title="' + renderDayName(opts, i) + '">' + renderDayName(opts, i, true) + '</abbr></th>'); |
|
308 |
+ } |
|
309 |
+ return '<thead>' + (opts.isRTL ? arr.reverse() : arr).join('') + '</thead>'; |
|
310 |
+ }, |
|
311 |
+ |
|
312 |
+ renderTitle = function(instance, c, year, month, refYear) |
|
313 |
+ { |
|
314 |
+ var i, j, arr, |
|
315 |
+ opts = instance._o, |
|
316 |
+ isMinYear = year === opts.minYear, |
|
317 |
+ isMaxYear = year === opts.maxYear, |
|
318 |
+ html = '<div class="pika-title">', |
|
319 |
+ monthHtml, |
|
320 |
+ yearHtml, |
|
321 |
+ prev = true, |
|
322 |
+ next = true; |
|
323 |
+ |
|
324 |
+ for (arr = [], i = 0; i < 12; i++) { |
|
325 |
+ arr.push('<option value="' + (year === refYear ? i - c : 12 + i - c) + '"' + |
|
326 |
+ (i === month ? ' selected': '') + |
|
327 |
+ ((isMinYear && i < opts.minMonth) || (isMaxYear && i > opts.maxMonth) ? 'disabled' : '') + '>' + |
|
328 |
+ opts.i18n.months[i] + '</option>'); |
|
329 |
+ } |
|
330 |
+ monthHtml = '<div class="pika-label">' + opts.i18n.months[month] + '<select class="pika-select pika-select-month">' + arr.join('') + '</select></div>'; |
|
331 |
+ |
|
332 |
+ if (isArray(opts.yearRange)) { |
|
333 |
+ i = opts.yearRange[0]; |
|
334 |
+ j = opts.yearRange[1] + 1; |
|
335 |
+ } else { |
|
336 |
+ i = year - opts.yearRange; |
|
337 |
+ j = 1 + year + opts.yearRange; |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ for (arr = []; i < j && i <= opts.maxYear; i++) { |
|
341 |
+ if (i >= opts.minYear) { |
|
342 |
+ arr.push('<option value="' + i + '"' + (i === year ? ' selected': '') + '>' + (i) + '</option>'); |
|
343 |
+ } |
|
344 |
+ } |
|
345 |
+ yearHtml = '<div class="pika-label">' + year + opts.yearSuffix + '<select class="pika-select pika-select-year">' + arr.join('') + '</select></div>'; |
|
346 |
+ |
|
347 |
+ if (opts.showMonthAfterYear) { |
|
348 |
+ html += yearHtml + monthHtml; |
|
349 |
+ } else { |
|
350 |
+ html += monthHtml + yearHtml; |
|
351 |
+ } |
|
352 |
+ |
|
353 |
+ if (isMinYear && (month === 0 || opts.minMonth >= month)) { |
|
354 |
+ prev = false; |
|
355 |
+ } |
|
356 |
+ |
|
357 |
+ if (isMaxYear && (month === 11 || opts.maxMonth <= month)) { |
|
358 |
+ next = false; |
|
359 |
+ } |
|
360 |
+ |
|
361 |
+ if (c === 0) { |
|
362 |
+ html += '<button class="pika-prev' + (prev ? '' : ' is-disabled') + '" type="button">' + opts.i18n.previousMonth + '</button>'; |
|
363 |
+ } |
|
364 |
+ if (c === (instance._o.numberOfMonths - 1) ) { |
|
365 |
+ html += '<button class="pika-next' + (next ? '' : ' is-disabled') + '" type="button">' + opts.i18n.nextMonth + '</button>'; |
|
366 |
+ } |
|
367 |
+ |
|
368 |
+ return html += '</div>'; |
|
369 |
+ }, |
|
370 |
+ |
|
371 |
+ renderTable = function(opts, data) |
|
372 |
+ { |
|
373 |
+ return '<table cellpadding="0" cellspacing="0" class="pika-table">' + renderHead(opts) + renderBody(data) + '</table>'; |
|
374 |
+ }, |
|
375 |
+ |
|
376 |
+ |
|
377 |
+ /** |
|
378 |
+ * Pikaday constructor |
|
379 |
+ */ |
|
380 |
+ Pikaday = function(options) |
|
381 |
+ { |
|
382 |
+ var self = this, |
|
383 |
+ opts = self.config(options); |
|
384 |
+ |
|
385 |
+ self._onMouseDown = function(e) |
|
386 |
+ { |
|
387 |
+ if (!self._v) { |
|
301 | 388 |
return; |
302 | 389 |
} |
303 |
- else if (hasClass(target, 'pika-prev')) { |
|
304 |
- self.prevMonth(); |
|
390 |
+ e = e || window.event; |
|
391 |
+ var target = e.target || e.srcElement; |
|
392 |
+ if (!target) { |
|
393 |
+ return; |
|
305 | 394 |
} |
306 |
- else if (hasClass(target, 'pika-next')) { |
|
307 |
- self.nextMonth(); |
|
395 |
+ |
|
396 |
+ if (!hasClass(target, 'is-disabled')) { |
|
397 |
+ if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty')) { |
|
398 |
+ self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day'))); |
|
399 |
+ if (opts.bound) { |
|
400 |
+ sto(function() { |
|
401 |
+ self.hide(); |
|
402 |
+ if (opts.field) { |
|
403 |
+ opts.field.blur(); |
|
404 |
+ } |
|
405 |
+ }, 100); |
|
406 |
+ } |
|
407 |
+ return; |
|
408 |
+ } |
|
409 |
+ else if (hasClass(target, 'pika-prev')) { |
|
410 |
+ self.prevMonth(); |
|
411 |
+ } |
|
412 |
+ else if (hasClass(target, 'pika-next')) { |
|
413 |
+ self.nextMonth(); |
|
414 |
+ } |
|
308 | 415 |
} |
309 |
- } |
|
310 |
- if (!hasClass(target, 'pika-select')) { |
|
311 |
- if (e.preventDefault) { |
|
312 |
- e.preventDefault(); |
|
416 |
+ if (!hasClass(target, 'pika-select')) { |
|
417 |
+ if (e.preventDefault) { |
|
418 |
+ e.preventDefault(); |
|
419 |
+ } else { |
|
420 |
+ e.returnValue = false; |
|
421 |
+ return false; |
|
422 |
+ } |
|
313 | 423 |
} else { |
314 |
- return e.returnValue = false; |
|
424 |
+ self._c = true; |
|
315 | 425 |
} |
316 |
- } else { |
|
317 |
- self._c = true; |
|
318 |
- } |
|
319 |
- }; |
|
426 |
+ }; |
|
320 | 427 |
|
321 |
- self._onChange = function(e) |
|
322 |
- { |
|
323 |
- e = e || window.event; |
|
324 |
- var target = e.target || e.srcElement; |
|
325 |
- if (!target) { |
|
326 |
- return; |
|
327 |
- } |
|
328 |
- if (hasClass(target, 'pika-select-month')) { |
|
329 |
- self.gotoMonth(target.value); |
|
330 |
- } |
|
331 |
- else if (hasClass(target, 'pika-select-year')) { |
|
332 |
- self.gotoYear(target.value); |
|
333 |
- } |
|
334 |
- }; |
|
428 |
+ self._onChange = function(e) |
|
429 |
+ { |
|
430 |
+ e = e || window.event; |
|
431 |
+ var target = e.target || e.srcElement; |
|
432 |
+ if (!target) { |
|
433 |
+ return; |
|
434 |
+ } |
|
435 |
+ if (hasClass(target, 'pika-select-month')) { |
|
436 |
+ self.gotoMonth(target.value); |
|
437 |
+ } |
|
438 |
+ else if (hasClass(target, 'pika-select-year')) { |
|
439 |
+ self.gotoYear(target.value); |
|
440 |
+ } |
|
441 |
+ }; |
|
335 | 442 |
|
336 |
- self._onInputChange = function(e) |
|
337 |
- { |
|
338 |
- if (hasMoment) { |
|
339 |
- self.setDate(window.moment(opts.field.value, opts.format).toDate()); |
|
340 |
- } |
|
341 |
- else { |
|
342 |
- var date = new Date(Date.parse(opts.field.value)); |
|
443 |
+ self._onInputChange = function(e) |
|
444 |
+ { |
|
445 |
+ var date; |
|
446 |
+ |
|
447 |
+ if (e.firedBy === self) { |
|
448 |
+ return; |
|
449 |
+ } |
|
450 |
+ if (hasMoment) { |
|
451 |
+ date = moment(opts.field.value, opts.format); |
|
452 |
+ date = (date && date.isValid()) ? date.toDate() : null; |
|
453 |
+ } |
|
454 |
+ else { |
|
455 |
+ date = new Date(Date.parse(opts.field.value)); |
|
456 |
+ } |
|
343 | 457 |
self.setDate(isDate(date) ? date : null); |
344 |
- } |
|
345 |
- if (!self._v) { |
|
346 |
- self.show(); |
|
347 |
- } |
|
348 |
- }; |
|
349 |
- |
|
350 |
- self._onInputFocus = function(e) |
|
351 |
- { |
|
352 |
- self.show(); |
|
353 |
- }; |
|
354 |
- |
|
355 |
- self._onInputClick = function(e) |
|
356 |
- { |
|
357 |
- self.show(); |
|
358 |
- }; |
|
359 |
- |
|
360 |
- self._onInputBlur = function(e) |
|
361 |
- { |
|
362 |
- if (!self._c) { |
|
363 |
- self._b = sto(function() { |
|
364 |
- self.hide(); |
|
365 |
- }, 50); |
|
366 |
- } |
|
367 |
- self._c = false; |
|
368 |
- }; |
|
369 |
- |
|
370 |
- self._onClick = function(e) |
|
371 |
- { |
|
372 |
- e = e || window.event; |
|
373 |
- var target = e.target || e.srcElement, |
|
374 |
- pEl = target; |
|
375 |
- if (!target) { |
|
376 |
- return; |
|
377 |
- } |
|
378 |
- if (!hasEventListeners && hasClass(target, 'pika-select')) { |
|
379 |
- if (!target.onchange) { |
|
380 |
- target.setAttribute('onchange', 'return;'); |
|
381 |
- addEvent(target, 'change', self._onChange); |
|
458 |
+ if (!self._v) { |
|
459 |
+ self.show(); |
|
382 | 460 |
} |
383 |
- } |
|
384 |
- do { |
|
385 |
- if (hasClass(pEl, 'pika-single')) { |
|
461 |
+ }; |
|
462 |
+ |
|
463 |
+ self._onInputFocus = function() |
|
464 |
+ { |
|
465 |
+ self.show(); |
|
466 |
+ }; |
|
467 |
+ |
|
468 |
+ self._onInputClick = function() |
|
469 |
+ { |
|
470 |
+ self.show(); |
|
471 |
+ }; |
|
472 |
+ |
|
473 |
+ self._onInputBlur = function() |
|
474 |
+ { |
|
475 |
+ if (!self._c) { |
|
476 |
+ self._b = sto(function() { |
|
477 |
+ self.hide(); |
|
478 |
+ }, 50); |
|
479 |
+ } |
|
480 |
+ self._c = false; |
|
481 |
+ }; |
|
482 |
+ |
|
483 |
+ self._onClick = function(e) |
|
484 |
+ { |
|
485 |
+ e = e || window.event; |
|
486 |
+ var target = e.target || e.srcElement, |
|
487 |
+ pEl = target; |
|
488 |
+ if (!target) { |
|
386 | 489 |
return; |
387 | 490 |
} |
491 |
+ if (!hasEventListeners && hasClass(target, 'pika-select')) { |
|
492 |
+ if (!target.onchange) { |
|
493 |
+ target.setAttribute('onchange', 'return;'); |
|
494 |
+ addEvent(target, 'change', self._onChange); |
|
495 |
+ } |
|
496 |
+ } |
|
497 |
+ do { |
|
498 |
+ if (hasClass(pEl, 'pika-single')) { |
|
499 |
+ return; |
|
500 |
+ } |
|
501 |
+ } |
|
502 |
+ while ((pEl = pEl.parentNode)); |
|
503 |
+ if (self._v && target !== opts.trigger) { |
|
504 |
+ self.hide(); |
|
505 |
+ } |
|
506 |
+ }; |
|
507 |
+ |
|
508 |
+ self.el = document.createElement('div'); |
|
509 |
+ self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : ''); |
|
510 |
+ |
|
511 |
+ addEvent(self.el, 'mousedown', self._onMouseDown, true); |
|
512 |
+ addEvent(self.el, 'change', self._onChange); |
|
513 |
+ |
|
514 |
+ if (opts.field) { |
|
515 |
+ if (opts.container) { |
|
516 |
+ opts.container.appendChild(self.el); |
|
517 |
+ } else if (opts.bound) { |
|
518 |
+ document.body.appendChild(self.el); |
|
519 |
+ } else { |
|
520 |
+ opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling); |
|
521 |
+ } |
|
522 |
+ addEvent(opts.field, 'change', self._onInputChange); |
|
523 |
+ |
|
524 |
+ if (!opts.defaultDate) { |
|
525 |
+ if (hasMoment && opts.field.value) { |
|
526 |
+ opts.defaultDate = moment(opts.field.value, opts.format).toDate(); |
|
527 |
+ } else { |
|
528 |
+ opts.defaultDate = new Date(Date.parse(opts.field.value)); |
|
529 |
+ } |
|
530 |
+ opts.setDefaultDate = true; |
|
531 |
+ } |
|
388 | 532 |
} |
389 |
- while ((pEl = pEl.parentNode)); |
|
390 |
- if (self._v && target !== opts.field) { |
|
391 |
- self.hide(); |
|
533 |
+ |
|
534 |
+ var defDate = opts.defaultDate; |
|
535 |
+ |
|
536 |
+ if (isDate(defDate)) { |
|
537 |
+ if (opts.setDefaultDate) { |
|
538 |
+ self.setDate(defDate, true); |
|
539 |
+ } else { |
|
540 |
+ self.gotoDate(defDate); |
|
541 |
+ } |
|
542 |
+ } else { |
|
543 |
+ self.gotoDate(new Date()); |
|
544 |
+ } |
|
545 |
+ |
|
546 |
+ if (opts.bound) { |
|
547 |
+ this.hide(); |
|
548 |
+ self.el.className += ' is-bound'; |
|
549 |
+ addEvent(opts.trigger, 'click', self._onInputClick); |
|
550 |
+ addEvent(opts.trigger, 'focus', self._onInputFocus); |
|
551 |
+ addEvent(opts.trigger, 'blur', self._onInputBlur); |
|
552 |
+ } else { |
|
553 |
+ this.show(); |
|
392 | 554 |
} |
393 | 555 |
}; |
394 |
- |
|
395 |
- self.el = document.createElement('div'); |
|
396 |
- self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : ''); |
|
397 |
- |
|
398 |
- addEvent(self.el, 'mousedown', self._onMouseDown, true); |
|
399 |
- addEvent(self.el, 'change', self._onChange); |
|
400 |
- |
|
401 |
- if (opts.field) { |
|
402 |
- if (opts.bound) { |
|
403 |
- document.body.appendChild(self.el); |
|
404 |
- } else { |
|
405 |
- opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling); |
|
406 |
- } |
|
407 |
- addEvent(opts.field, 'change', self._onInputChange); |
|
408 |
- |
|
409 |
- if (!opts.defaultDate) { |
|
410 |
- if (hasMoment && opts.field.value) { |
|
411 |
- opts.defaultDate = window.moment(opts.field.value, opts.format).toDate(); |
|
412 |
- } else { |
|
413 |
- opts.defaultDate = new Date(Date.parse(opts.field.value)); |
|
414 |
- } |
|
415 |
- opts.setDefaultDate = true; |
|
416 |
- } |
|
417 |
- } |
|
418 |
- |
|
419 |
- var defDate = opts.defaultDate; |
|
420 |
- |
|
421 |
- if (isDate(defDate)) { |
|
422 |
- if (opts.setDefaultDate) { |
|
423 |
- self.setDate(defDate); |
|
424 |
- } else { |
|
425 |
- self.gotoDate(defDate); |
|
426 |
- } |
|
427 |
- } else { |
|
428 |
- self.gotoDate(new Date()); |
|
429 |
- } |
|
430 |
- |
|
431 |
- if (opts.bound) { |
|
432 |
- this.hide(); |
|
433 |
- self.el.className += ' is-bound'; |
|
434 |
- addEvent(opts.field, 'click', self._onInputClick); |
|
435 |
- addEvent(opts.field, 'focus', self._onInputFocus); |
|
436 |
- addEvent(opts.field, 'blur', self._onInputBlur); |
|
437 |
- } else { |
|
438 |
- this.show(); |
|
439 |
- } |
|
440 |
- |
|
441 |
- }; |
|
442 | 556 |
|
443 | 557 |
|
444 | 558 |
/** |
445 | 559 |
* public Pikaday API |
446 | 560 |
*/ |
447 |
- window.Pikaday.prototype = { |
|
561 |
+ Pikaday.prototype = { |
|
448 | 562 |
|
449 | 563 |
|
450 | 564 |
/** |
... | ... | @@ -464,6 +578,8 @@ |
464 | 578 |
|
465 | 579 |
opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field); |
466 | 580 |
|
581 |
+ opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field; |
|
582 |
+ |
|
467 | 583 |
var nom = parseInt(opts.numberOfMonths, 10) || 1; |
468 | 584 |
opts.numberOfMonths = nom > 4 ? 4 : nom; |
469 | 585 |
|
... | ... | @@ -477,11 +593,13 @@ |
477 | 593 |
opts.maxDate = opts.minDate = false; |
478 | 594 |
} |
479 | 595 |
if (opts.minDate) { |
480 |
- opts.minYear = opts.minDate.getFullYear(); |
|
596 |
+ setToStartOfDay(opts.minDate); |
|
597 |
+ opts.minYear = opts.minDate.getFullYear(); |
|
481 | 598 |
opts.minMonth = opts.minDate.getMonth(); |
482 | 599 |
} |
483 | 600 |
if (opts.maxDate) { |
484 |
- opts.maxYear = opts.maxDate.getFullYear(); |
|
601 |
+ setToStartOfDay(opts.maxDate); |
|
602 |
+ opts.maxYear = opts.maxDate.getFullYear(); |
|
485 | 603 |
opts.maxMonth = opts.maxDate.getMonth(); |
486 | 604 |
} |
487 | 605 |
|
... | ... | @@ -504,7 +622,7 @@ |
504 | 622 |
*/ |
505 | 623 |
toString: function(format) |
506 | 624 |
{ |
507 |
- return !isDate(this._d) ? '' : hasMoment ? window.moment(this._d).format(format || this._o.format) : this._d.toDateString(); |
|
625 |
+ return !isDate(this._d) ? '' : hasMoment ? moment(this._d).format(format || this._o.format) : this._d.toDateString(); |
|
508 | 626 |
}, |
509 | 627 |
|
510 | 628 |
/** |
... | ... | @@ -512,7 +630,17 @@ |
512 | 630 |
*/ |
513 | 631 |
getMoment: function() |
514 | 632 |
{ |
515 |
- return hasMoment ? window.moment(this._d) : null; |
|
633 |
+ return hasMoment ? moment(this._d) : null; |
|
634 |
+ }, |
|
635 |
+ |
|
636 |
+ /** |
|
637 |
+ * set the current selection from a Moment.js object (if available) |
|
638 |
+ */ |
|
639 |
+ setMoment: function(date, preventOnSelect) |
|
640 |
+ { |
|
641 |
+ if (hasMoment && moment.isMoment(date)) { |
|
642 |
+ this.setDate(date.toDate(), preventOnSelect); |
|
643 |
+ } |
|
516 | 644 |
}, |
517 | 645 |
|
518 | 646 |
/** |
... | ... | @@ -526,7 +654,7 @@ |
526 | 654 |
/** |
527 | 655 |
* set the current selection |
528 | 656 |
*/ |
529 |
- setDate: function(date) |
|
657 |
+ setDate: function(date, preventOnSelect) |
|
530 | 658 |
{ |
531 | 659 |
if (!date) { |
532 | 660 |
this._d = null; |
... | ... | @@ -549,13 +677,14 @@ |
549 | 677 |
} |
550 | 678 |
|
551 | 679 |
this._d = new Date(date.getTime()); |
552 |
- this._d.setHours(0,0,0,0); |
|
680 |
+ setToStartOfDay(this._d); |
|
553 | 681 |
this.gotoDate(this._d); |
554 | 682 |
|
555 | 683 |
if (this._o.field) { |
556 | 684 |
this._o.field.value = this.toString(); |
685 |
+ fireEvent(this._o.field, 'change', { firedBy: this }); |
|
557 | 686 |
} |
558 |
- if (typeof this._o.onSelect === 'function') { |
|
687 |
+ if (!preventOnSelect && typeof this._o.onSelect === 'function') { |
|
559 | 688 |
this._o.onSelect.call(this, this.getDate()); |
560 | 689 |
} |
561 | 690 |
}, |
... | ... | @@ -565,11 +694,43 @@ |
565 | 694 |
*/ |
566 | 695 |
gotoDate: function(date) |
567 | 696 |
{ |
697 |
+ var newCalendar = true; |
|
698 |
+ |
|
568 | 699 |
if (!isDate(date)) { |
569 | 700 |
return; |
570 | 701 |
} |
571 |
- this._y = date.getFullYear(); |
|
572 |
- this._m = date.getMonth(); |
|
702 |
+ |
|
703 |
+ if (this.calendars) { |
|
704 |
+ var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1), |
|
705 |
+ lastVisibleDate = new Date(this.calendars[this.calendars.length-1].year, this.calendars[this.calendars.length-1].month, 1), |
|
706 |
+ visibleDate = date.getTime(); |
|
707 |
+ // get the end of the month |
|
708 |
+ lastVisibleDate.setMonth(lastVisibleDate.getMonth()+1); |
|
709 |
+ lastVisibleDate.setDate(lastVisibleDate.getDate()-1); |
|
710 |
+ newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate); |
|
711 |
+ } |
|
712 |
+ |
|
713 |
+ if (newCalendar) { |
|
714 |
+ this.calendars = [{ |
|
715 |
+ month: date.getMonth(), |
|
716 |
+ year: date.getFullYear() |
|
717 |
+ }]; |
|
718 |
+ if (this._o.mainCalendar === 'right') { |
|
719 |
+ this.calendars[0].month += 1 - this._o.numberOfMonths; |
|
720 |
+ } |
|
721 |
+ } |
|
722 |
+ |
|
723 |
+ this.adjustCalendars(); |
|
724 |
+ }, |
|
725 |
+ |
|
726 |
+ adjustCalendars: function() { |
|
727 |
+ this.calendars[0] = adjustCalendar(this.calendars[0]); |
|
728 |
+ for (var c = 1; c < this._o.numberOfMonths; c++) { |
|
729 |
+ this.calendars[c] = adjustCalendar({ |
|
730 |
+ month: this.calendars[0].month + c, |
|
731 |
+ year: this.calendars[0].year |
|
732 |
+ }); |
|
733 |
+ } |
|
573 | 734 |
this.draw(); |
574 | 735 |
}, |
575 | 736 |
|
... | ... | @@ -583,28 +744,22 @@ |
583 | 744 |
*/ |
584 | 745 |
gotoMonth: function(month) |
585 | 746 |
{ |
586 |
- if (!isNaN( (month = parseInt(month, 10)) )) { |
|
587 |
- this._m = month < 0 ? 0 : month > 11 ? 11 : month; |
|
588 |
- this.draw(); |
|
747 |
+ if (!isNaN(month)) { |
|
748 |
+ this.calendars[0].month = parseInt(month, 10); |
|
749 |
+ this.adjustCalendars(); |
|
589 | 750 |
} |
590 | 751 |
}, |
591 | 752 |
|
592 | 753 |
nextMonth: function() |
593 | 754 |
{ |
594 |
- if (++this._m > 11) { |
|
595 |
- this._m = 0; |
|
596 |
- this._y++; |
|
597 |
- } |
|
598 |
- this.draw(); |
|
755 |
+ this.calendars[0].month++; |
|
756 |
+ this.adjustCalendars(); |
|
599 | 757 |
}, |
600 | 758 |
|
601 | 759 |
prevMonth: function() |
602 | 760 |
{ |
603 |
- if (--this._m < 0) { |
|
604 |
- this._m = 11; |
|
605 |
- this._y--; |
|
606 |
- } |
|
607 |
- this.draw(); |
|
761 |
+ this.calendars[0].month--; |
|
762 |
+ this.adjustCalendars(); |
|
608 | 763 |
}, |
609 | 764 |
|
610 | 765 |
/** |
... | ... | @@ -613,9 +768,25 @@ |
613 | 768 |
gotoYear: function(year) |
614 | 769 |
{ |
615 | 770 |
if (!isNaN(year)) { |
616 |
- this._y = parseInt(year, 10); |
|
617 |
- this.draw(); |
|
771 |
+ this.calendars[0].year = parseInt(year, 10); |
|
772 |
+ this.adjustCalendars(); |
|
618 | 773 |
} |
774 |
+ }, |
|
775 |
+ |
|
776 |
+ /** |
|
777 |
+ * change the minDate |
|
778 |
+ */ |
|
779 |
+ setMinDate: function(value) |
|
780 |
+ { |
|
781 |
+ this._o.minDate = value; |
|
782 |
+ }, |
|
783 |
+ |
|
784 |
+ /** |
|
785 |
+ * change the maxDate |
|
786 |
+ */ |
|
787 |
+ setMaxDate: function(value) |
|
788 |
+ { |
|
789 |
+ this._o.maxDate = value; |
|
619 | 790 |
}, |
620 | 791 |
|
621 | 792 |
/** |
... | ... | @@ -630,7 +801,8 @@ |
630 | 801 |
minYear = opts.minYear, |
631 | 802 |
maxYear = opts.maxYear, |
632 | 803 |
minMonth = opts.minMonth, |
633 |
- maxMonth = opts.maxMonth; |
|
804 |
+ maxMonth = opts.maxMonth, |
|
805 |
+ html = ''; |
|
634 | 806 |
|
635 | 807 |
if (this._y <= minYear) { |
636 | 808 |
this._y = minYear; |
... | ... | @@ -645,21 +817,73 @@ |
645 | 817 |
} |
646 | 818 |
} |
647 | 819 |
|
648 |
- this.el.innerHTML = renderTitle(this) + this.render(this._y, this._m); |
|
820 |
+ for (var c = 0; c < opts.numberOfMonths; c++) { |
|
821 |
+ html += '<div class="pika-lendar">' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year) + this.render(this.calendars[c].year, this.calendars[c].month) + '</div>'; |
|
822 |
+ } |
|
823 |
+ |
|
824 |
+ this.el.innerHTML = html; |
|
649 | 825 |
|
650 | 826 |
if (opts.bound) { |
651 |
- var pEl = opts.field, |
|
652 |
- left = pEl.offsetLeft, |
|
653 |
- top = pEl.offsetTop + pEl.offsetHeight; |
|
827 |
+ if(opts.field.type !== 'hidden') { |
|
828 |
+ sto(function() { |
|
829 |
+ opts.trigger.focus(); |
|
830 |
+ }, 1); |
|
831 |
+ } |
|
832 |
+ } |
|
833 |
+ |
|
834 |
+ if (typeof this._o.onDraw === 'function') { |
|
835 |
+ var self = this; |
|
836 |
+ sto(function() { |
|
837 |
+ self._o.onDraw.call(self); |
|
838 |
+ }, 0); |
|
839 |
+ } |
|
840 |
+ }, |
|
841 |
+ |
|
842 |
+ adjustPosition: function() |
|
843 |
+ { |
|
844 |
+ if (this._o.container) return; |
|
845 |
+ var field = this._o.trigger, pEl = field, |
|
846 |
+ width = this.el.offsetWidth, height = this.el.offsetHeight, |
|
847 |
+ viewportWidth = window.innerWidth || document.documentElement.clientWidth, |
|
848 |
+ viewportHeight = window.innerHeight || document.documentElement.clientHeight, |
|
849 |
+ scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop, |
|
850 |
+ left, top, clientRect; |
|