Commit 03cf7c38882c8b034e49719d3360dd5556ca8bc3
1 parent
22787c6693
Exists in
master
minor changes due to ui updation and integrated schedular
Showing
9 changed files
with
10869 additions
and
102 deletions
Show diff stats
app/css/custom.css
1 | /*------------------------------------------------------------------ | 1 | /*------------------------------------------------------------------ |
2 | 2 | ||
3 | [Custom Stylesheet] | 3 | [Custom Stylesheet] |
4 | 4 | ||
5 | 5 | ||
6 | Last change: 03/25/2013 | 6 | Last change: 03/25/2013 |
7 | Assigned to: You | 7 | Assigned to: You |
8 | 8 | ||
9 | -------------------------------------------------------------------*/ | 9 | -------------------------------------------------------------------*/ |
10 | 10 | ||
11 | body{ | 11 | body{ |
12 | color: #333; | 12 | color: #333; |
13 | } | 13 | } |
14 | 14 | ||
15 | .custom-widget-content{ | 15 | .custom-widget-content{ |
16 | width: 100%; | 16 | width: 100%; |
17 | height: auto; | 17 | height: auto; |
18 | padding: 15px 0; | 18 | padding: 15px 0; |
19 | background-color: #fff; | 19 | background-color: #fff; |
20 | border: 1px solid #D5D5D5; | 20 | border: 1px solid #D5D5D5; |
21 | border-radius: 0 0 5px 5px; | 21 | border-radius: 0 0 5px 5px; |
22 | } | 22 | } |
23 | .hr{ | 23 | .hr{ |
24 | margin: 5px 0; | 24 | margin: 5px 0; |
25 | } | 25 | } |
26 | .disableSelect:disabled{ | 26 | .disableSelect:disabled{ |
27 | border: 0; | 27 | border: 0; |
28 | background-color: transparent; | 28 | background-color: transparent; |
29 | color: #f90; | 29 | color: #f90; |
30 | box-shadow: none; | 30 | box-shadow: none; |
31 | -webkit-appearance: none; | 31 | -webkit-appearance: none; |
32 | appearance: none; | 32 | appearance: none; |
33 | } | 33 | } |
34 | .disableInput:disabled{ | 34 | .disableInput:disabled{ |
35 | border: 0; | 35 | border: 0; |
36 | background-color: transparent; | 36 | background-color: transparent; |
37 | color: #222; | 37 | color: #222; |
38 | box-shadow: none; | 38 | box-shadow: none; |
39 | } | 39 | } |
40 | .img-logo{ | 40 | .img-logo{ |
41 | height: 25px; | 41 | height: 25px; |
42 | padding-left: 10%; | 42 | padding-left: 10%; |
43 | } | 43 | } |
44 | 44 | ||
45 | .widget-header > i{ | 45 | .widget-header > i{ |
46 | display: inline-block; | 46 | display: inline-block; |
47 | margin-top: -3px; | 47 | margin-top: -3px; |
48 | margin-left: 13px; | 48 | margin-left: 13px; |
49 | margin-right: -2px; | 49 | margin-right: -2px; |
50 | font-size: 16px; | 50 | font-size: 16px; |
51 | color: #555; | 51 | color: #555; |
52 | vertical-align: middle; | 52 | vertical-align: middle; |
53 | } | 53 | } |
54 | 54 | ||
55 | 55 | ||
56 | table{ | 56 | table{ |
57 | width: 100%; | 57 | width: 100%; |
58 | } | 58 | } |
59 | table th, td{ | 59 | table th, td{ |
60 | font-size: 13px; | 60 | font-size: 13px; |
61 | } | 61 | } |
62 | .navbar-brand{ | 62 | .navbar-brand{ |
63 | height: auto; | 63 | height: auto; |
64 | } | 64 | } |
65 | .custom-widget-content .btn-xs{ | 65 | .custom-widget-content .btn-xs{ |
66 | padding: 3px 5px; | 66 | padding: 3px 5px; |
67 | } | 67 | } |
68 | .fc-event{ | 68 | .fc-event{ |
69 | background-color: #ff9900 !important; | 69 | background-color: #ff9900 !important; |
70 | color: #fff !important; | 70 | color: #fff !important; |
71 | border: 1px solid #ff9900 !important; | 71 | border: 1px solid #ff9900 !important; |
72 | } | 72 | } |
73 | .pull-right-color{ | 73 | .pull-right-color{ |
74 | background-image: linear-gradient(to bottom, #1c84c6 0%, #1c84c6 100%); | 74 | background-image: linear-gradient(to bottom, #1c84c6 0%, #1c84c6 100%); |
75 | } | 75 | } |
76 | .pull-right-setup{ | 76 | .pull-right-setup{ |
77 | margin-bottom: 10px; | 77 | margin-bottom: 10px; |
78 | background-image: linear-gradient(to bottom, #1c84c6 0%, #1c84c6 100%); | 78 | background-image: linear-gradient(to bottom, #1c84c6 0%, #1c84c6 100%); |
79 | } | 79 | } |
80 | .Airport{ | 80 | .Airport{ |
81 | padding-top: 5px; | 81 | padding-top: 5px; |
82 | } | 82 | } |
83 | .src-image{ | 83 | .src-image{ |
84 | width: 50px; | 84 | width: 50px; |
85 | height: 50px; | 85 | height: 50px; |
86 | } | 86 | } |
87 | .Accept{ | 87 | .Accept{ |
88 | padding-top: 25px; | 88 | padding-top: 25px; |
89 | } | 89 | } |
90 | .dis{ | 90 | .dis{ |
91 | float: left; | 91 | float: left; |
92 | margin-right:100px; | 92 | margin-right:100px; |
93 | } | 93 | } |
94 | /*.email-password{ | 94 | /*.email-password{ |
95 | width: 40px; | 95 | width: 40px; |
96 | height: 24px; | 96 | height: 24px; |
97 | }*/ | 97 | }*/ |
98 | .email-password-icon{ | 98 | .email-password-icon{ |
99 | width: 10px; | 99 | width: 10px; |
100 | height: 10px; | 100 | height: 10px; |
101 | } | 101 | } |
102 | @media( max-width: 768px;){ | 102 | @media( max-width: 768px;){ |
103 | .btn{ | 103 | .btn{ |
104 | font-size: 10px !important; | 104 | font-size: 10px !important; |
105 | } | 105 | } |
106 | .dis{ | 106 | .dis{ |
107 | float: none; | 107 | float: none; |
108 | margin-right: 0px; | 108 | margin-right: 0px; |
109 | } | 109 | } |
110 | } | 110 | } |
111 | .Search-tabs{ | 111 | .Search-tabs{ |
112 | border-bottom: 0px solid #ddd; */ | 112 | border-bottom: 0px solid #ddd; */ |
113 | } | 113 | } |
114 | 114 | ||
115 | .customer-table table{ | 115 | .customer-table table{ |
116 | width: 100%; | 116 | width: 100%; |
117 | } | 117 | } |
118 | .customer-table input{ | 118 | .customer-table input{ |
119 | border-radius: 0; | 119 | border-radius: 0; |
120 | box-shadow: none; | 120 | box-shadow: none; |
121 | border: 1px solid #e4e3e3; | 121 | border: 1px solid #e4e3e3; |
122 | padding: 0px; | 122 | padding: 0px; |
123 | font-size: 10px; | 123 | font-size: 10px; |
124 | display: -webkit-inline-box; | 124 | display: -webkit-inline-box; |
125 | display: inline-box; | 125 | display: inline-box; |
126 | height: 31px; | 126 | height: 31px; |
127 | } | 127 | } |
128 | .customer-table select{ | 128 | .customer-table select{ |
129 | border-radius: 0; | 129 | border-radius: 0; |
130 | box-shadow: none; | 130 | box-shadow: none; |
131 | border: 1px solid #e4e3e3; | 131 | border: 1px solid #e4e3e3; |
132 | padding: 0px; | 132 | padding: 0px; |
133 | font-size: 10px; | 133 | font-size: 10px; |
134 | height: 31px; | 134 | height: 31px; |
135 | display: -webkit-inline-box; | 135 | display: -webkit-inline-box; |
136 | display: inline-box; | 136 | display: inline-box; |
137 | } | 137 | } |
138 | .customer-table th > label{ | 138 | .customer-table th > label{ |
139 | font-weight: bold; | 139 | font-weight: bold; |
140 | padding: 10px 5px 2px 5px; | 140 | padding: 10px 5px 2px 5px; |
141 | margin-bottom: 0; | 141 | margin-bottom: 0; |
142 | } | 142 | } |
143 | .customer-table td{ | 143 | .customer-table td{ |
144 | padding: 5px !important; | 144 | padding: 5px !important; |
145 | height: auto !important; | 145 | height: auto !important; |
146 | vertical-align: middle !important; | 146 | vertical-align: middle !important; |
147 | } | 147 | } |
148 | .customer-table td > select{ | 148 | .customer-table td > select{ |
149 | height: 22px !important; | 149 | height: 22px !important; |
150 | } | 150 | } |
151 | .customer-table td > button{ | 151 | .customer-table td > button{ |
152 | font-size: 10px; | 152 | font-size: 10px; |
153 | } | 153 | } |
154 | 154 | ||
155 | /*.new-input-label{ | 155 | /*.new-input-label{ |
156 | margin-bottom: 0; | 156 | margin-bottom: 0; |
157 | line-height: 34px; | 157 | line-height: 34px; |
158 | }*/ | 158 | }*/ |
159 | 159 | ||
160 | 160 | ||
161 | 161 | ||
162 | 162 | ||
163 | 163 | ||
164 | /* new custom css for accrodian tabs */ | 164 | /* new custom css for accrodian tabs */ |
165 | 165 | ||
166 | .customAccordianHeader{ | 166 | .customAccordianHeader{ |
167 | width: 100%; | 167 | width: 100%; |
168 | height: auto; | 168 | height: auto; |
169 | background-color: #F3F3F3; | 169 | background-color: #F3F3F3; |
170 | border: 1px solid #ddd; | 170 | border: 1px solid #ddd; |
171 | color: #333; | 171 | color: #333; |
172 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35); | 172 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35); |
173 | border-radius: 3px 3px 3px 3px; | 173 | border-radius: 3px 3px 3px 3px; |
174 | padding: 10px 15px; | 174 | padding: 10px 15px; |
175 | margin-top: 10px; | 175 | margin-top: 10px; |
176 | } | 176 | } |
177 | 177 | ||
178 | .customAccordianHeader.customActive{ | 178 | .customAccordianHeader.customActive{ |
179 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#555555), to(#3b3b3b)); | 179 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#555555), to(#3b3b3b)); |
180 | background-image: linear-gradient(to bottom, #555555 0%, #3b3b3b 100%); | 180 | background-image: linear-gradient(to bottom, #555555 0%, #3b3b3b 100%); |
181 | background-repeat: repeat-x; | 181 | background-repeat: repeat-x; |
182 | border: 1px solid #222222; | 182 | border: 1px solid #222222; |
183 | color: #FFF; | 183 | color: #FFF; |
184 | border-radius: 3px 3px 0 0; | 184 | border-radius: 3px 3px 0 0; |
185 | } | 185 | } |
186 | .customAccordianHeader > span{ | 186 | .customAccordianHeader > span{ |
187 | font-size: 15px; | 187 | font-size: 15px; |
188 | margin-right: 20px; | ||
189 | line-height: 22px; | 188 | line-height: 22px; |
190 | float: left; | 189 | float: left; |
191 | } | 190 | } |
192 | .customAccordianHeader .btn{ | 191 | .customAccordianHeader .btn{ |
193 | padding: 2px 12px; | 192 | padding: 2px 12px; |
194 | } | 193 | } |
195 | .customAccordianHeader > .form-control{ | 194 | .customAccordianHeader > .form-control{ |
196 | outline: none; | 195 | outline: none; |
197 | box-shadow: none; | 196 | box-shadow: none; |
198 | } | 197 | } |
199 | .customAccordianHeader > select.form-control{ | 198 | .customAccordianHeader > select.form-control{ |
200 | width: 150px; | 199 | width: 150px; |
201 | height: 26px; | 200 | height: 26px; |
202 | padding: 0 6px; | 201 | padding: 0 6px; |
203 | margin-right: 20px; | ||
204 | float: left; | 202 | float: left; |
205 | } | 203 | } |
206 | .customAccordianHeader > select.form-control[disabled]{ | 204 | .customAccordianHeader > select.form-control[disabled]{ |
207 | border: none; | 205 | border: none; |
208 | -webkit-appearance: none; | 206 | -webkit-appearance: none; |
209 | -moz-appearance: none; | 207 | -moz-appearance: none; |
210 | appearance: none; | 208 | appearance: none; |
211 | background-color: transparent; | 209 | background-color: transparent; |
212 | color: #f90; | 210 | color: #f90; |
213 | width: auto; | 211 | width: auto; |
214 | } | 212 | } |
215 | .customAccordianHeader > input.form-control{ | 213 | .customAccordianHeader > input.form-control{ |
216 | width: 80px; | 214 | width: 70px; |
217 | height: 26px; | 215 | height: 26px; |
218 | padding: 0 6px; | 216 | padding: 0 6px; |
219 | float: left; | 217 | float: left; |
220 | } | 218 | } |
221 | .customAccordianHeader > input.form-control[disabled]{ | 219 | .customAccordianHeader > input.form-control[disabled]{ |
222 | border: none; | 220 | border: none; |
223 | -webkit-appearance: none; | 221 | -webkit-appearance: none; |
224 | -moz-appearance: none; | 222 | -moz-appearance: none; |
225 | appearance: none; | 223 | appearance: none; |
226 | background-color: transparent; | 224 | background-color: transparent; |
227 | color: #333; | 225 | color: #333; |
228 | } | 226 | } |
229 | .customAccordianTabBody{ | 227 | .customAccordianTabBody{ |
230 | width: 100%; | 228 | width: 100%; |
231 | height: auto; | 229 | height: auto; |
232 | padding: 15px; | 230 | padding: 15px; |
233 | border: 1px solid #ddd; | 231 | border: 1px solid #ddd; |
234 | border-top: 0; | 232 | border-top: 0; |
235 | border-radius: 0 0 3px 3px; | 233 | border-radius: 0 0 3px 3px; |
236 | } | 234 | } |
237 | .customTableWithFilter{ | 235 | .customTableWithFilter{ |
238 | width: auto; | 236 | width: auto; |
239 | height: auto; | 237 | height: auto; |
240 | } | 238 | } |
241 | .customTableWithFilter th{ | 239 | .customTableWithFilter th{ |
242 | padding: 5px; | 240 | padding: 5px; |
243 | background-color: #f3f3f3; | 241 | background-color: #f3f3f3; |
244 | border: 1px solid #ddd; | 242 | border: 1px solid #ddd; |
245 | } | 243 | } |
246 | .customTableWithFilter th i{ | 244 | .customTableWithFilter th i{ |
247 | margin-top: 3px; | 245 | margin-top: 3px; |
248 | } | 246 | } |
249 | .customTableWithFilter th span{ | 247 | .customTableWithFilter th span{ |
250 | font-size: 11px; | 248 | font-size: 11px; |
251 | color: #222; | 249 | color: #222; |
252 | } | 250 | } |
253 | .customTableWithFilter th input{ | 251 | .customTableWithFilter th input{ |
254 | width: 50px; | 252 | width: 50px; |
255 | } | 253 | } |
256 | .customTableWithFilter td{ | 254 | .customTableWithFilter td{ |
257 | padding: 5px; | 255 | padding: 5px; |
258 | border: 1px solid #ddd; | 256 | border: 1px solid #ddd; |
259 | } | 257 | } |
260 | .customTableWithFilter td span{ | 258 | .customTableWithFilter td span{ |
261 | font-size: 11px; | 259 | font-size: 11px; |
262 | } | 260 | } |
263 | .customTableWithFilter td span:first-child{ | 261 | .customTableWithFilter td span:first-child{ |
264 | color: #449d44; | 262 | color: #449d44; |
265 | font-weight: bold; | 263 | font-weight: bold; |
266 | } | 264 | } |
267 | .customTableWithFilter td span:last-child{ | 265 | .customTableWithFilter td span:last-child{ |
268 | color: #c9302c; | 266 | color: #c9302c; |
269 | font-style: italic; | 267 | font-style: italic; |
270 | font-weight: bold; | 268 | font-weight: bold; |
271 | } | 269 | } |
272 | .customTableWithFilter td .tierTextBox{ | 270 | .customTableWithFilter td .tierTextBox{ |
273 | width: 70px; | 271 | width: 70px; |
274 | } | 272 | } |
275 | .customTableWithFilter .addTierButton{ | 273 | .customTableWithFilter .addTierButton{ |
276 | color: #fff; | 274 | color: #fff; |
277 | background-color: #2196f3; | 275 | background-color: #2196f3; |
278 | border: none; | 276 | border: none; |
279 | padding: 3px 5px; | 277 | padding: 3px 5px; |
280 | } | 278 | } |
281 | .customTableWithFilter td .fa-trash-o{ | 279 | .customTableWithFilter td .fa-trash-o{ |
282 | color: #fff; | 280 | color: #fff; |
283 | background-color: #c9302c; | 281 | background-color: #c9302c; |
284 | padding: 5px; | 282 | padding: 5px; |
285 | border-radius: 3px; | 283 | border-radius: 3px; |
286 | cursor: pointer; | 284 | cursor: pointer; |
287 | } | 285 | } |
288 | .resizeTextarea{ | 286 | .resizeTextarea{ |
289 | resize: vertical; | 287 | resize: vertical; |
290 | margin-top: 20px; | 288 | margin-top: 20px; |
291 | } | 289 | } |
292 | 290 | ||
293 | 291 | ||
294 | 292 | ||
295 | 293 | ||
296 | 294 | ||
297 | .new-widget-content{ | 295 | .new-widget-content{ |
298 | padding: 0 0 0 0 !important; | 296 | padding: 0 0 0 0 !important; |
299 | } | 297 | } |
300 | .new-tab-heading{ | 298 | .new-tab-heading{ |
301 | padding: 5px; | 299 | padding: 5px; |
302 | color: #333; | 300 | color: #333; |
303 | background-color: #fcfcfc; | 301 | background-color: #fcfcfc; |
304 | border-bottom: 1px solid #d5d5d5; | 302 | border-bottom: 1px solid #d5d5d5; |
305 | border-right: 1px solid #d5d5d5; | 303 | border-right: 1px solid #d5d5d5; |
306 | } | 304 | } |
307 | .new-tab-body{ | 305 | .new-tab-body{ |
308 | border-right: 1px solid #d5d5d5; | 306 | border-right: 1px solid #d5d5d5; |
309 | border-bottom: 1px solid #d5d5d5; | 307 | border-bottom: 1px solid #d5d5d5; |
310 | padding-left: 15px; | 308 | padding-left: 15px; |
311 | padding-bottom: 5px; | 309 | padding-bottom: 5px; |
312 | } | 310 | } |
313 | .new-tab-heading i.fa{ | 311 | .new-tab-heading i.fa{ |
314 | font-size: 16px; | 312 | font-size: 16px; |
315 | cursor: pointer; | 313 | cursor: pointer; |
316 | } | 314 | } |
317 | .new-tab-heading span{ | 315 | .new-tab-heading span{ |
318 | font-size: 14px; | 316 | font-size: 14px; |
319 | margin-left: 5px; | 317 | margin-left: 5px; |
320 | } | 318 | } |
321 | .new-tab-heading i.fa-plus-circle{ | 319 | .new-tab-heading i.fa-plus-circle{ |
322 | color: #067dfc; | 320 | color: #067dfc; |
323 | } | 321 | } |
324 | .new-tab-heading i.fa-minus-circle{ | 322 | .new-tab-heading i.fa-minus-circle{ |
325 | color: #d00d09; | 323 | color: #d00d09; |
326 | } | 324 | } |
327 | .new-tab-body-th{ | 325 | .new-tab-body-th{ |
328 | color: #333; | 326 | color: #333; |
329 | } | 327 | } |
330 | .new-tab-body-th > div{ | 328 | .new-tab-body-th > div{ |
331 | font-size: 12px; | 329 | font-size: 12px; |
332 | font-weight: bold; | 330 | font-weight: bold; |
333 | padding-right: 5px; | 331 | padding-right: 5px; |
334 | padding-left: 5px; | 332 | padding-left: 5px; |
335 | text-align: center; | 333 | text-align: center; |
336 | } | 334 | } |
337 | .new-tab-body-th > div:first-child{ | 335 | .new-tab-body-th > div:first-child{ |
338 | font-size: 14px; | 336 | font-size: 14px; |
339 | font-weight: bold; | 337 | font-weight: bold; |
340 | text-align: left; | 338 | text-align: left; |
341 | } | 339 | } |
342 | .new-tab-body-td i.fa-plus-circle{ | 340 | .new-tab-body-td i.fa-plus-circle{ |
343 | color: #067dfc; | 341 | color: #067dfc; |
344 | } | 342 | } |
345 | .new-tab-body-td i.fa-minus-circle{ | 343 | .new-tab-body-td i.fa-minus-circle{ |
346 | color: #d00d09; | 344 | color: #d00d09; |
347 | } | 345 | } |
348 | .new-tab-body-td input{ | 346 | .new-tab-body-td input{ |
349 | width: 50px; | 347 | width: 50px; |
350 | height: 24px; | 348 | height: 24px; |
351 | } | 349 | } |
352 | .new-tab-body-td .fa{ | 350 | .new-tab-body-td .fa{ |
353 | font-size: 14px; | 351 | font-size: 14px; |
354 | } | 352 | } |
355 | .new-tab-body-td > div:first-child > div{ | 353 | .new-tab-body-td > div:first-child > div{ |
356 | padding-right: 5px; | 354 | padding-right: 5px; |
357 | padding-left: 5px; | 355 | padding-left: 5px; |
358 | text-align: center; | 356 | text-align: center; |
359 | } | 357 | } |
360 | .new-tab-body-td > div:first-child > div:first-child{ | 358 | .new-tab-body-td > div:first-child > div:first-child{ |
361 | text-align: left; | 359 | text-align: left; |
362 | } | 360 | } |
363 | .appliesTableHeading{ | 361 | .appliesTableHeading{ |
364 | border: 1px solid #d5d5d5; | 362 | border: 1px solid #d5d5d5; |
365 | padding: 5px; | 363 | padding: 5px; |
366 | color: #333; | 364 | color: #333; |
367 | font-weight: bold; | 365 | font-weight: bold; |
368 | background-color: #fcfcfc; | 366 | background-color: #fcfcfc; |
369 | margin: 10px 0 0 0; | 367 | margin: 10px 0 0 0; |
370 | } | 368 | } |
371 | .appliesTableBody{ | 369 | .appliesTableBody{ |
372 | border: 1px solid #d5d5d5; | 370 | border: 1px solid #d5d5d5; |
373 | border-top: 0; | 371 | border-top: 0; |
374 | background-color: #ffffe0; | 372 | background-color: #ffffe0; |
375 | margin-bottom: 10px; | 373 | margin-bottom: 10px; |
376 | height: auto; | 374 | height: auto; |
377 | } | 375 | } |
378 | .appliesTableBody > div.firstDiv{ | 376 | .appliesTableBody > div.firstDiv{ |
379 | height: 100px; | 377 | height: 100px; |
380 | float: left; | 378 | float: left; |
381 | width: 40%; | 379 | width: 40%; |
382 | border-right: 1px solid #d5d5d5; | 380 | border-right: 1px solid #d5d5d5; |
383 | overflow: auto; | 381 | overflow: auto; |
384 | padding: 5px; | 382 | padding: 5px; |
385 | } | 383 | } |
386 | .appliesTableBody > div.lastDiv{ | 384 | .appliesTableBody > div.lastDiv{ |
387 | width: 59%; | 385 | width: 59%; |
388 | height: 100px; | 386 | height: 100px; |
389 | padding: 5px; | 387 | padding: 5px; |
390 | float: left; | 388 | float: left; |
391 | } | 389 | } |
392 | .appliesTableBody > div.lastDiv textarea{ | 390 | .appliesTableBody > div.lastDiv textarea{ |
393 | width: 40%; | 391 | width: 40%; |
394 | height: 60px; | 392 | height: 60px; |
395 | margin: 15px 0; | 393 | margin: 15px 0; |
396 | resize: none; | 394 | resize: none; |
397 | float: left; | 395 | float: left; |
398 | } | 396 | } |
399 | .reportedByTag{ | 397 | .reportedByTag{ |
400 | width: 60%; | 398 | width: 60%; |
401 | height: 80px; | 399 | height: 80px; |
402 | border-top: 10px solid #393; | 400 | border-top: 10px solid #393; |
403 | border-left: 10px solid #6c6; | 401 | border-left: 10px solid #6c6; |
404 | border-radius: 5px 0 0 5px; | 402 | border-radius: 5px 0 0 5px; |
405 | background-color: #d7d7d7; | 403 | background-color: #d7d7d7; |
406 | float: right; | 404 | float: right; |
407 | position: relative; | 405 | position: relative; |
408 | right: -13px; | 406 | right: -13px; |
409 | margin-top: 5px; | 407 | margin-top: 5px; |
410 | font-size: 11px; | 408 | font-size: 11px; |
411 | } | 409 | } |
412 | .confirmedTag{ | 410 | .confirmedTag{ |
413 | background-color: #393; | 411 | background-color: #393; |
414 | color: #fff; | 412 | color: #fff; |
415 | -webkit-transform: rotate(-90deg); | 413 | -webkit-transform: rotate(-90deg); |
416 | -moz-transform: rotate(-90deg); | 414 | -moz-transform: rotate(-90deg); |
417 | -o-transform: rotate(-90deg); | 415 | -o-transform: rotate(-90deg); |
418 | -ms-transform: rotate(-90deg); | 416 | -ms-transform: rotate(-90deg); |
419 | transform: rotate(-90deg); | 417 | transform: rotate(-90deg); |
420 | position: absolute; | 418 | position: absolute; |
421 | bottom: 25px; | 419 | bottom: 25px; |
422 | left: -24px; | 420 | left: -24px; |
423 | width: 71px; | 421 | width: 71px; |
424 | text-align: center; | 422 | text-align: center; |
425 | font-size: 11px; | 423 | font-size: 11px; |
426 | } | 424 | } |
427 | .reportedByTag table{ | 425 | .reportedByTag table{ |
428 | margin-left: 27px; | 426 | margin-left: 27px; |
429 | width: calc(100% - 27px); | 427 | width: calc(100% - 27px); |
430 | } | 428 | } |
431 | .reportedByTag table td{ | 429 | .reportedByTag table td{ |
432 | font-size: 11px; | 430 | font-size: 11px; |
433 | text-align: center; | 431 | text-align: center; |
434 | font-weight: bold; | 432 | font-weight: bold; |
435 | } | 433 | } |
436 | .reportedByTag table tr:last-child td{ | 434 | .reportedByTag table tr:last-child td{ |
437 | text-align: left; | 435 | text-align: left; |
438 | font-weight: normal; | 436 | font-weight: normal; |
439 | font-size: 10px; | 437 | font-size: 10px; |
440 | } | 438 | } |
441 | .feeManagerRight{ | 439 | .feeManagerRight{ |
442 | min-height: 70px; | 440 | min-height: 70px; |
443 | position: relative; | 441 | position: relative; |
444 | } | 442 | } |
445 | .feeManagerRight div{ | 443 | .feeManagerRight div{ |
446 | position: absolute; | 444 | position: absolute; |
447 | bottom: 0; | 445 | bottom: 0; |
448 | width: 100%; | 446 | width: 100%; |
449 | } | 447 | } |
450 | .new-tab-body-td{ | 448 | .new-tab-body-td{ |
451 | padding: 5px 0; | 449 | padding: 5px 0; |
452 | } | 450 | } |
453 | 451 | ||
454 | /* new checkbox slide css */ | 452 | /* new checkbox slide css */ |
455 | 453 | ||
456 | .onoffswitch { | 454 | .onoffswitch { |
457 | position: relative; width: 80px; | 455 | position: relative; width: 80px; |
458 | -webkit-user-select:none; | 456 | -webkit-user-select:none; |
459 | -moz-user-select:none; | 457 | -moz-user-select:none; |
460 | -ms-user-select: none; | 458 | -ms-user-select: none; |
461 | margin: auto; | 459 | margin: auto; |
462 | } | 460 | } |
463 | .onoffswitch-checkbox { | 461 | .onoffswitch-checkbox { |
464 | display: none; | 462 | display: none; |
465 | } | 463 | } |
466 | .onoffswitch-label { | 464 | .onoffswitch-label { |
467 | display: block; | 465 | display: block; |
468 | overflow: hidden; | 466 | overflow: hidden; |
469 | cursor: pointer; | 467 | cursor: pointer; |
470 | border: 2px solid #999999; | 468 | border: 2px solid #999999; |
471 | border-radius: 0px; | 469 | border-radius: 0px; |
472 | } | 470 | } |
473 | .onoffswitch-inner { | 471 | .onoffswitch-inner { |
474 | display: block; | 472 | display: block; |
475 | width: 200%; | 473 | width: 200%; |
476 | margin-left: -100%; | 474 | margin-left: -100%; |
477 | transition: margin 0.3s ease-in 0s; | 475 | transition: margin 0.3s ease-in 0s; |
478 | } | 476 | } |
479 | .onoffswitch-inner:before, .onoffswitch-inner:after { | 477 | .onoffswitch-inner:before, .onoffswitch-inner:after { |
480 | display: block; | 478 | display: block; |
481 | float: left; | 479 | float: left; |
482 | width: 50%; | 480 | width: 50%; |
483 | height: 18px; | 481 | height: 18px; |
484 | padding: 0; | 482 | padding: 0; |
485 | line-height: 18px; | 483 | line-height: 18px; |
486 | font-size: 10px; | 484 | font-size: 10px; |
487 | color: white; | 485 | color: white; |
488 | font-family: Trebuchet, Arial, sans-serif; | 486 | font-family: Trebuchet, Arial, sans-serif; |
489 | font-weight: bold; | 487 | font-weight: bold; |
490 | box-sizing: border-box; | 488 | box-sizing: border-box; |
491 | } | 489 | } |
492 | .onoffswitch-inner:before { | 490 | .onoffswitch-inner:before { |
493 | content: "Include"; | 491 | content: "Include"; |
494 | padding-left: 5px; | 492 | padding-left: 5px; |
495 | background-color: #016ADB; color: #FFFFFF; | 493 | background-color: #016ADB; color: #FFFFFF; |
496 | text-align: left; | 494 | text-align: left; |
497 | } | 495 | } |
498 | .onoffswitch-inner:after { | 496 | .onoffswitch-inner:after { |
499 | content: "Exclude"; | 497 | content: "Exclude"; |
500 | padding-right: 5px; | 498 | padding-right: 5px; |
501 | background-color: ; color: #333333; | 499 | background-color: ; color: #333333; |
502 | text-align: right; | 500 | text-align: right; |
503 | } | 501 | } |
504 | .onoffswitch-switch { | 502 | .onoffswitch-switch { |
505 | display: block; | 503 | display: block; |
506 | width: 25px; | 504 | width: 25px; |
507 | margin: -3.5px; | 505 | margin: -3.5px; |
508 | background: #FFFFFF; | 506 | background: #FFFFFF; |
509 | position: absolute; | 507 | position: absolute; |
510 | top: 0; | 508 | top: 0; |
511 | bottom: 0; | 509 | bottom: 0; |
512 | right: 58px; | 510 | right: 58px; |
513 | border: 2px solid #999999; | 511 | border: 2px solid #999999; |
514 | border-radius: 0px; | 512 | border-radius: 0px; |
515 | transition: all 0.3s ease-in 0s; | 513 | transition: all 0.3s ease-in 0s; |
516 | } | 514 | } |
517 | .onoffswitch-switch i{ | 515 | .onoffswitch-switch i{ |
518 | line-height: 25px; | 516 | line-height: 25px; |
519 | } | 517 | } |
520 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { | 518 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { |
521 | margin-left: 0; | 519 | margin-left: 0; |
522 | } | 520 | } |
523 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { | 521 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { |
524 | right: 0px; | 522 | right: 0px; |
525 | } | 523 | } |
526 | .onoffswitch-checkbox fa-check{ | 524 | .onoffswitch-checkbox fa-check{ |
527 | color: #00ff00; | 525 | color: #00ff00; |
528 | } | 526 | } |
529 | .onoffswitch-checkbox fa-times{ | 527 | .onoffswitch-checkbox fa-times{ |
530 | color: #ff0000; | 528 | color: #ff0000; |
531 | } | 529 | } |
532 | 530 | ||
533 | .newButtons{ | 531 | .newButtons{ |
534 | background-color: #fff; | 532 | background-color: #fff; |
535 | border-radius: 10px; | 533 | border-radius: 10px; |
536 | color: #333; | 534 | color: #333; |
537 | border: 1px solid #eee; | 535 | border: 1px solid #eee; |
538 | box-shadow: 5px 5px 6px rgba(0,0,0,0.3); | 536 | box-shadow: 5px 5px 6px rgba(0,0,0,0.3); |
539 | padding: 6px 15px; | 537 | padding: 6px 15px; |
540 | margin-right: 10px; | 538 | margin-right: 10px; |
541 | } | 539 | } |
542 | .newButtons:disabled{ | 540 | .newButtons:disabled{ |
543 | background-color: #eee; | 541 | background-color: #eee; |
544 | } | 542 | } |
545 | *{ | 543 | *{ |
546 | outline: none; | 544 | outline: none; |
547 | } | 545 | } |
548 | 546 | ||
549 | 547 | ||
550 | /* my custom modal */ | 548 | /* my custom modal */ |
551 | 549 | ||
552 | .customBackdrop{ | 550 | .customBackdrop{ |
553 | position: fixed; | 551 | position: fixed; |
554 | background-color: rgba(0, 0, 0, 0.15); | 552 | background-color: rgba(0, 0, 0, 0.15); |
555 | top: 0; | 553 | top: 0; |
556 | left: 0; | 554 | left: 0; |
557 | width: 100%; | 555 | width: 100%; |
558 | height: 100%; | 556 | height: 100%; |
559 | z-index: 999; | 557 | z-index: 999; |
560 | } | 558 | } |
561 | .customModalInner{ | 559 | .customModalInner{ |
562 | width: 100%; | 560 | width: 100%; |
563 | max-width: 500px; | 561 | max-width: 500px; |
564 | height: 200px; | 562 | height: 200px; |
565 | top: 15%; | 563 | top: 15%; |
566 | position: relative; | 564 | position: relative; |
567 | margin: auto; | 565 | margin: auto; |
568 | } | 566 | } |
569 | .customModelHead, .customModelFooter{ | 567 | .customModelHead, .customModelFooter{ |
570 | width: 100%; | 568 | width: 100%; |
571 | background-color: #fafafa; | 569 | background-color: #fafafa; |
572 | border-bottom: 1px solid #ccc; | 570 | border-bottom: 1px solid #ccc; |
573 | color: #333; | 571 | color: #333; |
574 | padding: 10px; | 572 | padding: 10px; |
575 | } | 573 | } |
576 | .customModelFooter{ | 574 | .customModelFooter{ |
577 | border-top: 1px solid #ccc; | 575 | border-top: 1px solid #ccc; |
578 | } | 576 | } |
579 | .customModelBody{ | 577 | .customModelBody{ |
580 | width: 100%; | 578 | width: 100%; |
581 | height: auto; | 579 | height: auto; |
582 | background-color: #fff; | 580 | background-color: #fff; |
583 | color: #333; | 581 | color: #333; |
584 | padding: 10px; | 582 | padding: 10px; |
585 | } | 583 | } |
586 | .customModelHead p{ | 584 | .customModelHead p{ |
587 | margin-bottom: 0; | 585 | margin-bottom: 0; |
588 | font-size: 14px; | 586 | font-size: 14px; |
589 | } | 587 | } |
590 | .customModelFooter button{ | 588 | .customModelFooter button{ |
591 | border: 1px solid #ccc; | 589 | border: 1px solid #ccc; |
592 | } | 590 | } |
593 | .customModelFooter input[type="submit"]{ | 591 | .customModelFooter input[type="submit"]{ |
594 | background-color: #6ad46a; | 592 | background-color: #6ad46a; |
595 | border: 1px solid #ccc; | 593 | border: 1px solid #ccc; |
596 | } | 594 | } |
597 | 595 | ||
598 | 596 | ||
599 | /* custom confirmation model */ | 597 | /* custom confirmation model */ |
600 | 598 | ||
601 | 599 | ||
602 | .customConfirmPopBackdrop{ | 600 | .customConfirmPopBackdrop{ |
603 | position: fixed; | 601 | position: fixed; |
604 | background-color: rgba(0, 0, 0, 0.3); | 602 | background-color: rgba(0, 0, 0, 0.3); |
605 | top: 0; | 603 | top: 0; |
606 | left: 0; | 604 | left: 0; |
607 | width: 100%; | 605 | width: 100%; |
608 | height: 100%; | 606 | height: 100%; |
609 | z-index: 999999999; | 607 | z-index: 999999999; |
610 | display: none; | 608 | display: none; |
611 | } | 609 | } |
612 | .customConfirmPopBackdrop .customModalInner{ | 610 | .customConfirmPopBackdrop .customModalInner{ |
613 | top: 30%; | 611 | top: 30%; |
614 | } | 612 | } |
615 | 613 | ||
616 | 614 | ||
617 | 615 | ||
618 | /* tier list table */ | 616 | /* tier list table */ |
619 | 617 | ||
620 | .tierListWrap{ | 618 | .tierListWrap{ |
621 | width: 120px; | 619 | width: 120px; |
622 | height: auto; | 620 | height: auto; |
623 | float: left; | 621 | float: left; |
624 | } | 622 | } |
625 | .tierListHead{ | 623 | .tierListHead{ |
626 | width: 100%; | 624 | width: 100%; |
627 | padding: 5px; | 625 | padding: 5px; |
628 | background-color: #f3f3f3; | 626 | background-color: #f3f3f3; |
629 | border: 1px solid #ddd; | 627 | border: 1px solid #ddd; |
630 | border-right: none; | 628 | border-right: none; |
631 | } | 629 | } |
632 | .tierListBody{ | 630 | .tierListBody{ |
633 | width: 100%; | 631 | width: 100%; |
634 | padding: 5px; | 632 | padding: 5px; |
635 | border: 1px solid #ddd; | 633 | border: 1px solid #ddd; |
636 | border-top: none; | 634 | border-top: none; |
637 | border-right: none; | 635 | border-right: none; |
638 | } | 636 | } |
639 | .minTierSpan{ | 637 | .minTierSpan{ |
640 | color: #449d44; | 638 | color: #449d44; |
641 | font-weight: bold; | 639 | font-weight: bold; |
642 | font-size: 11px; | 640 | font-size: 11px; |
643 | } | 641 | } |
644 | .maxTierSpan{ | 642 | .maxTierSpan{ |
645 | color: #c9302c; | 643 | color: #c9302c; |
646 | font-style: italic; | 644 | font-style: italic; |
647 | font-size: 11px; | 645 | font-size: 11px; |
648 | font-weight: bold; | 646 | font-weight: bold; |
649 | } | 647 | } |
650 | .tierHeadingSpan{ | 648 | .tierHeadingSpan{ |
651 | font-size: 11px; | 649 | font-size: 11px; |
652 | color: #222; | 650 | color: #222; |
653 | font-weight: bold; | 651 | font-weight: bold; |
654 | } | 652 | } |
655 | .tierListHead input{ | 653 | .tierListHead input{ |
656 | width: 50px; | 654 | width: 50px; |
657 | height: 24px; | 655 | height: 24px; |
658 | } | 656 | } |
659 | .addTierBtn{ | 657 | .addTierBtn{ |
660 | color: #fff; | 658 | color: #fff; |
661 | background-color: #2196f3; | 659 | background-color: #2196f3; |
662 | border: none; | 660 | border: none; |
663 | padding: 1px 5px; | 661 | padding: 1px 5px; |
664 | } | 662 | } |
665 | .deleteTierIcon{ | 663 | .deleteTierIcon{ |
666 | color: #fff; | 664 | color: #fff; |
667 | background-color: #c9302c; | 665 | background-color: #c9302c; |
668 | padding: 5px; | 666 | padding: 5px; |
669 | border-radius: 3px; | 667 | border-radius: 3px; |
670 | cursor: pointer; | 668 | cursor: pointer; |
671 | } | 669 | } |
672 | 670 | ||
673 | .customToogle { | 671 | .customToogle { |
674 | padding: 2px 6px !important; | 672 | padding: 2px 6px !important; |
675 | height: auto !important; | 673 | height: auto !important; |
676 | min-height: 28px !important; | 674 | min-height: 28px !important; |
677 | 675 | ||
678 | } | 676 | } |
679 | 677 | ||
680 | .myLoader{ | 678 | .myLoader{ |
681 | width: 100%; | 679 | width: 100%; |
682 | height: 100%; | 680 | height: 100%; |
683 | position: fixed; | 681 | position: fixed; |
684 | z-index: 9999; | 682 | z-index: 9999; |
685 | top: 0; | 683 | top: 0; |
686 | left: 0; | 684 | left: 0; |
687 | text-align: center; | 685 | text-align: center; |
688 | background-color: rgba(0, 0, 0, 0.2); | 686 | background-color: rgba(0, 0, 0, 0.2); |
689 | } | 687 | } |
690 | .myLoader img{ | 688 | .myLoader img{ |
691 | position: relative; | 689 | position: relative; |
692 | top: calc(50% - 25px); | 690 | top: calc(50% - 25px); |
693 | } | 691 | } |
694 | .customErrorInput{ | 692 | .customErrorInput{ |
695 | background-color: rgb(251, 227, 228); | 693 | background-color: rgb(251, 227, 228); |
696 | border: 1px solid #fbc2c4; | 694 | border: 1px solid #fbc2c4; |
697 | color: #8a1f11; | 695 | color: #8a1f11; |
698 | } | 696 | } |
699 | .customErrorMessage{ | 697 | .customErrorMessage{ |
700 | color: #8a1f11; | 698 | color: #8a1f11; |
701 | font-weight: bold; | 699 | font-weight: bold; |
702 | } | 700 | } |
703 | .ui-datepicker .ui-widget-header{ | 701 | .ui-datepicker .ui-widget-header{ |
704 | background-image: none !important; | 702 | background-image: none !important; |
705 | } | 703 | } |
706 | .recordCountSelect{ | 704 | .recordCountSelect{ |
707 | background-color: #fff; | 705 | background-color: #fff; |
708 | } | 706 | } |
709 | .exportBtn{ | 707 | .exportBtn{ |
710 | margin-top: 20px; | 708 | margin-top: 20px; |
711 | } | 709 | } |
712 | .select2-container .select2-choice{ | 710 | .select2-container .select2-choice{ |
713 | height: 34px !important; | 711 | height: 34px !important; |
714 | padding: 6px 12px !important; | 712 | padding: 6px 12px !important; |
715 | border: 1px solid #ccc !important; | 713 | border: 1px solid #ccc !important; |
716 | background-image: none !important; | 714 | background-image: none !important; |
717 | } | 715 | } |
718 | .select2-container .select2-choice .select2-arrow{ | 716 | .select2-container .select2-choice .select2-arrow{ |
719 | border-left: none !important; | 717 | border-left: none !important; |
720 | background: transparent !important; | 718 | background: transparent !important; |
721 | } | 719 | } |
722 | .select2-container .select2-choice{ | 720 | .select2-container .select2-choice{ |
723 | padding: 2px 12px !important; | 721 | padding: 2px 12px !important; |
724 | } | 722 | } |
app/index.html
1 | <html ng-app="acufuel"> | 1 | <html ng-app="acufuel"> |
2 | <head lang="en"> | 2 | <head lang="en"> |
3 | <meta charset="utf-8"> | 3 | <meta charset="utf-8"> |
4 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | 4 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
5 | <meta name="description" content=""> | 5 | <meta name="description" content=""> |
6 | <meta name="author" content=""> | 6 | <meta name="author" content=""> |
7 | <title>Acufuel</title> | 7 | <title>Acufuel</title> |
8 | <!-- styles --> | 8 | <!-- styles --> |
9 | <link rel="stylesheet" href="css/ui-lightness/jquery-ui-1.10.0.custom.min.css"/> | 9 | <link rel="stylesheet" href="css/ui-lightness/jquery-ui-1.10.0.custom.min.css"/> |
10 | <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"/> | 10 | <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"/> |
11 | <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css"/> | 11 | <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css"/> |
12 | <link rel="stylesheet" href="bower_components/bootstrap-toggle/css/bootstrap-toggle.min.css" /> | 12 | <link rel="stylesheet" href="bower_components/bootstrap-toggle/css/bootstrap-toggle.min.css" /> |
13 | <!-- <link rel="stylesheet" href="bower_components/fullcalendar/dist/fullcalendar.min.css"/> | 13 | <!-- <link rel="stylesheet" href="bower_components/fullcalendar/dist/fullcalendar.min.css"/> |
14 | <link rel="stylesheet" href="bower_components/fullcalendar/dist/fullcalendar.print.min.css"/> --> | 14 | <link rel="stylesheet" href="bower_components/fullcalendar/dist/fullcalendar.print.min.css"/> --> |
15 | <link rel="stylesheet" href="bower_components/toastr/toastr.min.css"/> | 15 | <link rel="stylesheet" href="bower_components/toastr/toastr.min.css"/> |
16 | <link rel="stylesheet" href="bower_components/angular-bootstrap/ui-bootstrap-csp.css"/> | 16 | <link rel="stylesheet" href="bower_components/angular-bootstrap/ui-bootstrap-csp.css"/> |
17 | <link href="bower_components/jqGrid/css/ui.jqgrid.css" rel="stylesheet"> | 17 | <link href="bower_components/jqGrid/css/ui.jqgrid.css" rel="stylesheet"> |
18 | <link href="bower_components/angular-xeditable/dist/css/xeditable.css" rel="stylesheet"> | 18 | <link href="bower_components/angular-xeditable/dist/css/xeditable.css" rel="stylesheet"> |
19 | <link href="bower_components/angular-bootstrap-toggle/dist/angular-bootstrap-toggle.min.css" | 19 | <link href="bower_components/angular-bootstrap-toggle/dist/angular-bootstrap-toggle.min.css" |
20 | rel="stylesheet"> | 20 | rel="stylesheet"> |
21 | 21 | ||
22 | 22 | ||
23 | <link rel="stylesheet" href="css/main.css"/> | 23 | <link rel="stylesheet" href="css/main.css"/> |
24 | <link rel="stylesheet" href="css/base-admin-3.css"/> | 24 | <link rel="stylesheet" href="css/base-admin-3.css"/> |
25 | <link rel="stylesheet" href="css/base-admin-3-responsive.css"/> | 25 | <link rel="stylesheet" href="css/base-admin-3-responsive.css"/> |
26 | <link rel="stylesheet" href="css/custom.css"/> | 26 | <link rel="stylesheet" href="css/custom.css"/> |
27 | 27 | ||
28 | <link href="css/bootstrap-responsive.min.css" rel="stylesheet"> | 28 | <link href="css/bootstrap-responsive.min.css" rel="stylesheet"> |
29 | <link href="http://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600" rel="stylesheet"> | 29 | <link href="http://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600" rel="stylesheet"> |
30 | <link href="js/plugins/cirque/cirque.css" rel="stylesheet"> | 30 | <link href="js/plugins/cirque/cirque.css" rel="stylesheet"> |
31 | 31 | ||
32 | <link href="css/pages/dashboard.css" rel="stylesheet"> | 32 | <link href="css/pages/dashboard.css" rel="stylesheet"> |
33 | <link href="js/plugins/faq/faq.css" rel="stylesheet"> | 33 | <link href="js/plugins/faq/faq.css" rel="stylesheet"> |
34 | <link href="css/pages/plans.css" rel="stylesheet"> | 34 | <link href="css/pages/plans.css" rel="stylesheet"> |
35 | 35 | ||
36 | <link href="js/plugins/lightbox/themes/evolution-dark/jquery.lightbox.css" rel="stylesheet"> | 36 | <link href="js/plugins/lightbox/themes/evolution-dark/jquery.lightbox.css" rel="stylesheet"> |
37 | <link href="js/plugins/msgGrowl/css/msgGrowl.css" rel="stylesheet"> | 37 | <link href="js/plugins/msgGrowl/css/msgGrowl.css" rel="stylesheet"> |
38 | <link href="js/plugins/msgbox/jquery.msgbox.css" rel="stylesheet"> | 38 | <link href="js/plugins/msgbox/jquery.msgbox.css" rel="stylesheet"> |
39 | <link href="css/pages/pricing.css" rel="stylesheet"> | 39 | <link href="css/pages/pricing.css" rel="stylesheet"> |
40 | <link href="css/pages/reports.css" rel="stylesheet"> | 40 | <link href="css/pages/reports.css" rel="stylesheet"> |
41 | 41 | ||
42 | <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/smoothness/jquery-ui.min.css" rel="stylesheet" type="text/css" /> | 42 | <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/smoothness/jquery-ui.min.css" rel="stylesheet" type="text/css" /> |
43 | 43 | ||
44 | <link rel="stylesheet" type="text/css" href="css/ngTable.css"> | 44 | <link rel="stylesheet" type="text/css" href="css/ngTable.css"> |
45 | <link rel="stylesheet" href="bower_components/select2/select2.css"> | 45 | <link rel="stylesheet" href="bower_components/select2/select2.css"> |
46 | 46 | ||
47 | <!-- <link rel="stylesheet" type="text/css" href="css/fullcalender.css"> | 47 | <!-- <link rel="stylesheet" type="text/css" href="css/fullcalender.css"> |
48 | <link rel="stylesheet" type="text/css" href="css/fullcalenderprint.css"> --> | 48 | <link rel="stylesheet" type="text/css" href="css/fullcalenderprint.css"> --> |
49 | 49 | ||
50 | <!-- <link rel="stylesheet"; href="https://unpkg.com/ng-table@2.0.2/bundles/ng-table.min.css"> --> | 50 | <!-- <link rel="stylesheet"; href="https://unpkg.com/ng-table@2.0.2/bundles/ng-table.min.css"> --> |
51 | 51 | ||
52 | <!-- <link href='https://fullcalendar.io/js/fullcalendar-3.4.0/fullcalendar.min.css' rel='stylesheet' /> | 52 | <!-- <link href='https://fullcalendar.io/js/fullcalendar-3.4.0/fullcalendar.min.css' rel='stylesheet' /> |
53 | <link href='https://fullcalendar.io/js/fullcalendar-3.4.0/fullcalendar.print.min.css' rel='stylesheet' media='print' /> | 53 | <link href='https://fullcalendar.io/js/fullcalendar-3.4.0/fullcalendar.print.min.css' rel='stylesheet' media='print' /> |
54 | <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/lib/moment.min.js'></script> | 54 | <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/lib/moment.min.js'></script> |
55 | <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/lib/jquery.min.js'></script> | 55 | <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/lib/jquery.min.js'></script> |
56 | <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/lib/jquery-ui.min.js'></script> --> | 56 | <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/lib/jquery-ui.min.js'></script> --> |
57 | 57 | <link rel="stylesheet" href="bower_components/fullcalendar/dist/fullcalendar.css"/> | |
58 | 58 | ||
59 | </head> | 59 | </head> |
60 | <body> | 60 | <body> |
61 | <!-- views --> | 61 | <!-- views --> |
62 | 62 | ||
63 | <!-- scripts --> | 63 | <!-- scripts --> |
64 | 64 | ||
65 | <script src="bower_components/jquery/dist/jquery.min.js"></script> | 65 | <script src="bower_components/jquery/dist/jquery.min.js"></script> |
66 | <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script> | 66 | <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script> |
67 | <script src="js/libs/jquery-ui-1.10.0.custom.min.js"></script> | 67 | <script src="js/libs/jquery-ui-1.10.0.custom.min.js"></script> |
68 | <!--<script src="js/plugins/flot/jquery.flot.js"></script> | 68 | <!--<script src="js/plugins/flot/jquery.flot.js"></script> |
69 | <script src="js/plugins/flot/jquery.flot.pie.js"></script> | 69 | <script src="js/plugins/flot/jquery.flot.pie.js"></script> |
70 | <script src="js/plugins/flot/jquery.flot.resize.js"></script> | 70 | <script src="js/plugins/flot/jquery.flot.resize.js"></script> |
71 | <script src="js/plugins/flot/jquery.flot.orderBars.js"></script>--> | 71 | <script src="js/plugins/flot/jquery.flot.orderBars.js"></script>--> |
72 | <script src="js/plugins/hoverIntent/jquery.hoverIntent.minified.js"></script> | 72 | <script src="js/plugins/hoverIntent/jquery.hoverIntent.minified.js"></script> |
73 | <script src="js/plugins/lightbox/jquery.lightbox.min.js"></script> | 73 | <script src="js/plugins/lightbox/jquery.lightbox.min.js"></script> |
74 | <script src="js/plugins/validate/jquery.validate.js"></script> | 74 | <script src="js/plugins/validate/jquery.validate.js"></script> |
75 | <script src="js/plugins/msgbox/jquery.msgbox.min.js"></script> | 75 | <script src="js/plugins/msgbox/jquery.msgbox.min.js"></script> |
76 | 76 | ||
77 | <script src="bower_components/angular/angular.min.js"></script> | 77 | <script src="bower_components/angular/angular.min.js"></script> |
78 | <script src="bower_components/angular-route/angular-route.js"></script> | 78 | <script src="bower_components/angular-route/angular-route.js"></script> |
79 | <script src="bower_components/angular-cookies/angular-cookies.min.js"></script> | 79 | <script src="bower_components/angular-cookies/angular-cookies.min.js"></script> |
80 | <script src="bower_components/angular-resource/angular-resource.min.js"></script> | 80 | <script src="bower_components/angular-resource/angular-resource.min.js"></script> |
81 | <script src="bower_components/angular-animate/angular-animate.js"></script> | 81 | <script src="bower_components/angular-animate/angular-animate.js"></script> |
82 | <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script> | 82 | <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script> |
83 | <script src="bower_components/bootstrap-toggle/js/bootstrap-toggle.min.js"></script> | 83 | <script src="bower_components/bootstrap-toggle/js/bootstrap-toggle.min.js"></script> |
84 | <script src="bower_components/angular-ui-router/release/angular-ui-router.min.js"></script> | 84 | <script src="bower_components/angular-ui-router/release/angular-ui-router.min.js"></script> |
85 | <script src='bower_components/moment/min/moment.min.js'></script> | 85 | <script src='bower_components/moment/min/moment.min.js'></script> |
86 | <!-- <script src="bower_components/fullcalendar/dist/fullcalendar.min.js"></script> --> | 86 | <!-- <script src="bower_components/fullcalendar/dist/fullcalendar.min.js"></script> --> |
87 | <script src='bower_components/toastr/toastr.min.js'></script> | 87 | <script src='bower_components/toastr/toastr.min.js'></script> |
88 | <script src="bower_components/angular-bootstrap/ui-bootstrap.min.js"></script> | 88 | <script src="bower_components/angular-bootstrap/ui-bootstrap.min.js"></script> |
89 | <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js"></script> | 89 | <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js"></script> |
90 | <script src="bower_components/jqGrid/js/i18n/grid.locale-en.js"></script> | 90 | <script src="bower_components/jqGrid/js/i18n/grid.locale-en.js"></script> |
91 | <script src="bower_components/jqGrid/js/jquery.jqGrid.min.js"></script> | 91 | <script src="bower_components/jqGrid/js/jquery.jqGrid.min.js"></script> |
92 | <script src="bower_components/angular-xeditable/dist/js/xeditable.js"></script> | 92 | <script src="bower_components/angular-xeditable/dist/js/xeditable.js"></script> |
93 | <script src="bower_components/angular-bootstrap-toggle/dist/angular-bootstrap-toggle.min.js"></script> | 93 | <script src="bower_components/angular-bootstrap-toggle/dist/angular-bootstrap-toggle.min.js"></script> |
94 | <script src="bower_components/select2/select2.js"></script> | 94 | <script src="bower_components/select2/select2.js"></script> |
95 | <script src="bower_components/angular-ui-select2/src/select2.js"></script> | 95 | <script src="bower_components/angular-ui-select2/src/select2.js"></script> |
96 | <script src="bower_components/angular-ckeditor/angular-ckeditor.js"></script> | 96 | <script src="bower_components/angular-ckeditor/angular-ckeditor.js"></script> |
97 | <script src="bower_components/angular-dragdrop/src/angular-dragdrop.min.js"></script> | ||
97 | 98 | ||
98 | <!-- <script src="https://unpkg.com/ng-table@2.0.2/bundles/ng-table.min.js"></script> --> | 99 | <!-- <script src="https://unpkg.com/ng-table@2.0.2/bundles/ng-table.min.js"></script> --> |
99 | 100 | <script type="text/javascript" src="bower_components/angular-ui-calendar/src/calendar.js"></script> | |
101 | <!-- <script type="text/javascript" src="bower_components/fullcalendar/dist/fullcalendar.min.js"></script> --> | ||
102 | |||
100 | 103 | ||
101 | <!-- | 104 | <!-- |
102 | <script src="scripts/inspinia.js"></script> | 105 | <script src="scripts/inspinia.js"></script> |
103 | <script src="plugins/iCheck/icheck.min.js"></script> | 106 | <script src="plugins/iCheck/icheck.min.js"></script> |
104 | --> | 107 | --> |
105 | 108 | ||
106 | <!-- <script src="./js/Application.js"></script> | 109 | <!-- <script src="./js/Application.js"></script> |
107 | <script src="js/charts/area.js"></script> | 110 | <script src="js/charts/area.js"></script> |
108 | <script src="js/charts/donut.js"></script> | 111 | <script src="js/charts/donut.js"></script> |
109 | <script src="js/charts/pie.js"></script> | 112 | <script src="js/charts/pie.js"></script> |
110 | <script src="js/charts/bar.js"></script> | 113 | <script src="js/charts/bar.js"></script> |
111 | <script src="js/demo/validation.js"></script> | 114 | <script src="js/demo/validation.js"></script> |
112 | <script src="js/charts/line.js"></script> --> | 115 | <script src="js/charts/line.js"></script> --> |
113 | <script src="js/demo/faq.js"></script> | 116 | <script src="js/demo/faq.js"></script> |
114 | <script src="js/plugins/faq/faq.js"></script> | 117 | <script src="js/plugins/faq/faq.js"></script> |
115 | <script src="js/demo/gallery.js"></script> | 118 | <script src="js/demo/gallery.js"></script> |
116 | <script src="js/demo/sliders.js"></script> | 119 | <script src="js/demo/sliders.js"></script> |
117 | <script src="js/plugins/msgGrowl/js/msgGrowl.js"></script> | 120 | <script src="js/plugins/msgGrowl/js/msgGrowl.js"></script> |
118 | <script src="js/demo/notifications.js"></script> | 121 | <script src="js/demo/notifications.js"></script> |
119 | 122 | ||
120 | <script src="js/ngTable.js"></script> | 123 | <script src="js/ngTable.js"></script> |
121 | 124 | ||
122 | <!-- <script src="js/fullcalender.js"></script> --> | 125 | <!-- <script src="js/fullcalender.js"></script> --> |
123 | 126 | ||
124 | <script src="http://cdn.ckeditor.com/4.6.1/standard-all/ckeditor.js"></script> | 127 | <script src="http://cdn.ckeditor.com/4.6.1/standard-all/ckeditor.js"></script> |
125 | <script src="https://code.highcharts.com/maps/highmaps.js"></script> | 128 | <script src="https://code.highcharts.com/maps/highmaps.js"></script> |
126 | <script src="https://code.highcharts.com/maps/modules/data.js"></script> | 129 | <script src="https://code.highcharts.com/maps/modules/data.js"></script> |
127 | <script src="https://code.highcharts.com/mapdata/countries/us/us-all.js"></script> | 130 | <script src="https://code.highcharts.com/mapdata/countries/us/us-all.js"></script> |
128 | <!-- Controllers --> | 131 | <!-- Controllers --> |
129 | <script src="js/app.js"></script> | 132 | <script src="js/app.js"></script> |
130 | <script src="js/app.constant.js"></script> | 133 | <script src="js/app.constant.js"></script> |
131 | <script src="partials/login/login.controller.js"></script> | 134 | <script src="partials/login/login.controller.js"></script> |
132 | <script src="partials/customers/customers.controller.js"></script> | 135 | <script src="partials/customers/customers.controller.js"></script> |
133 | <script src="partials/analytics/analytics.controller.js"></script> | 136 | <script src="partials/analytics/analytics.controller.js"></script> |
134 | <script src="partials/account/account.controller.js"></script> | 137 | <script src="partials/account/account.controller.js"></script> |
135 | <script src="partials/elements/elements.controller.js"></script> | 138 | <script src="partials/elements/elements.controller.js"></script> |
136 | <script src="partials/error/error.controller.js"></script> | 139 | <script src="partials/error/error.controller.js"></script> |
137 | <script src="partials/faq/faq.controller.js"></script> | 140 | <script src="partials/faq/faq.controller.js"></script> |
138 | <script src="partials/forms/forms.controller.js"></script> | 141 | <script src="partials/forms/forms.controller.js"></script> |
139 | <script src="partials/fuelManager/fuelManager.controller.js"></script> | 142 | <script src="partials/fuelManager/fuelManager.controller.js"></script> |
140 | <script src="partials/pricing/pricing.controller.js"></script> | 143 | <script src="partials/pricing/pricing.controller.js"></script> |
141 | <script src="partials/reports/reports.controller.js"></script> | 144 | <script src="partials/reports/reports.controller.js"></script> |
142 | <script src="partials/scheduler/scheduler.controller.js"></script> | 145 | <script src="partials/scheduler/scheduler.controller.js"></script> |
143 | <script src="partials/signup/signup.controller.js"></script> | 146 | <script src="partials/signup/signup.controller.js"></script> |
144 | <script src="partials/updateFuelManager/updateFuelManager.controller.js"></script> | 147 | <script src="partials/updateFuelManager/updateFuelManager.controller.js"></script> |
145 | <script src="partials/viewCompany/viewCompany.controller.js"></script> | 148 | <script src="partials/viewCompany/viewCompany.controller.js"></script> |
146 | <script src="partials/dashboard/dashboard.controller.js"></script> | 149 | <script src="partials/dashboard/dashboard.controller.js"></script> |
147 | <script src="partials/fuelOrders/fuelOrders.controller.js"></script> | 150 | <script src="partials/fuelOrders/fuelOrders.controller.js"></script> |
148 | <script src="partials/DispatchFuel/DispatchFuel.controller.js"></script> | 151 | <script src="partials/DispatchFuel/DispatchFuel.controller.js"></script> |
149 | <script src="partials/searchDispatchFuel/searchDispatchFuel.controller.js"></script> | 152 | <script src="partials/searchDispatchFuel/searchDispatchFuel.controller.js"></script> |
150 | <script src="partials/Accept/Accept.controller.js"></script> | 153 | <script src="partials/Accept/Accept.controller.js"></script> |
151 | <!-- <script src="partials/AircraftList/AircraftList.controller.js"></script> --> | 154 | <!-- <script src="partials/AircraftList/AircraftList.controller.js"></script> --> |
152 | <script src="partials/ContactView/ContactView.controller.js"></script> | 155 | <script src="partials/ContactView/ContactView.controller.js"></script> |
153 | <script src="partials/FuelVendors/FuelVendors.controller.js"></script> | 156 | <script src="partials/FuelVendors/FuelVendors.controller.js"></script> |
154 | <script src="partials/delselected/delselected.controller.js"></script> | 157 | <script src="partials/delselected/delselected.controller.js"></script> |
155 | <script src="partials/pricingcontact/pricingcontact.controller.js"></script> | 158 | <script src="partials/pricingcontact/pricingcontact.controller.js"></script> |
156 | <script src="partials/viewcontact/viewcontact.controller.js"></script> | 159 | <script src="partials/viewcontact/viewcontact.controller.js"></script> |
157 | <script src="partials/viewFuelVendor/viewFuelVendor.controller.js"></script> | 160 | <script src="partials/viewFuelVendor/viewFuelVendor.controller.js"></script> |
158 | <script src="partials/accountSetting/accountSetting.Controller.js"></script> | 161 | <script src="partials/accountSetting/accountSetting.Controller.js"></script> |
159 | <script src="partials/viewVendorContact/viewVendorContact.Controller.js"></script> | 162 | <script src="partials/viewVendorContact/viewVendorContact.Controller.js"></script> |
160 | <script src="partials/enterFuelOrder/enterFuelOrder.controller.js"></script> | 163 | <script src="partials/enterFuelOrder/enterFuelOrder.controller.js"></script> |
161 | 164 | ||
162 | <script src="partials/main/main.controller.js"></script> | 165 | <script src="partials/main/main.controller.js"></script> |
163 | <div ui-view></div> | 166 | <div ui-view></div> |
164 | <!-- End controllers --> | 167 | <!-- End controllers --> |
165 | 168 | ||
166 | 169 | ||
167 | <!-- services --> | 170 | <!-- services --> |
168 | <script src="partials/login/login.service.js"></script> | 171 | <script src="partials/login/login.service.js"></script> |
169 | <script src="partials/customers/customers.service.js"></script> | 172 | <script src="partials/customers/customers.service.js"></script> |
170 | <script src="partials/analytics/analytics.service.js"></script> | 173 | <script src="partials/analytics/analytics.service.js"></script> |
171 | <script src="partials/account/account.service.js"></script> | 174 | <script src="partials/account/account.service.js"></script> |
172 | <script src="partials/elements/elements.service.js"></script> | 175 | <script src="partials/elements/elements.service.js"></script> |
173 | <script src="partials/error/error.service.js"></script> | 176 | <script src="partials/error/error.service.js"></script> |
174 | <script src="partials/faq/faq.service.js"></script> | 177 | <script src="partials/faq/faq.service.js"></script> |
175 | <script src="partials/forms/forms.service.js"></script> | 178 | <script src="partials/forms/forms.service.js"></script> |
176 | <script src="partials/fuelManager/fuelManager.service.js"></script> | 179 | <script src="partials/fuelManager/fuelManager.service.js"></script> |
177 | <script src="partials/pricing/pricing.service.js"></script> | 180 | <script src="partials/pricing/pricing.service.js"></script> |
178 | <script src="partials/reports/reports.service.js"></script> | 181 | <script src="partials/reports/reports.service.js"></script> |
179 | <script src="partials/scheduler/scheduler.service.js"></script> | 182 | <script src="partials/scheduler/scheduler.service.js"></script> |
180 | <script src="partials/signup/signup.service.js"></script> | 183 | <script src="partials/signup/signup.service.js"></script> |
181 | <script src="partials/updateFuelManager/updateFuelManager.service.js"></script> | 184 | <script src="partials/updateFuelManager/updateFuelManager.service.js"></script> |
182 | <script src="partials/viewCompany/viewCompany.service.js"></script> | 185 | <script src="partials/viewCompany/viewCompany.service.js"></script> |
183 | <script src="partials/dashboard/dashboard.service.js"></script> | 186 | <script src="partials/dashboard/dashboard.service.js"></script> |
184 | <script src="partials/fuelOrders/fuelOrders.service.js"></script> | 187 | <script src="partials/fuelOrders/fuelOrders.service.js"></script> |
185 | <script src="partials/DispatchFuel/DispatchFuel.service.js"></script> | 188 | <script src="partials/DispatchFuel/DispatchFuel.service.js"></script> |
186 | <script src="partials/searchDispatchFuel/searchDispatchFuel.service.js"></script> | 189 | <script src="partials/searchDispatchFuel/searchDispatchFuel.service.js"></script> |
187 | <script src="partials/Accept/Accept.service.js"></script> | 190 | <script src="partials/Accept/Accept.service.js"></script> |
188 | <!-- <script src="partials/AircraftList/AircraftList.controller.js"></script> --> | 191 | <!-- <script src="partials/AircraftList/AircraftList.controller.js"></script> --> |
189 | <script src="partials/ContactView/ContactView.service.js"></script> | 192 | <script src="partials/ContactView/ContactView.service.js"></script> |
190 | <script src="partials/FuelVendors/FuelVendors.service.js"></script> | 193 | <script src="partials/FuelVendors/FuelVendors.service.js"></script> |
191 | <script src="partials/delselected/delselected.service.js"></script> | 194 | <script src="partials/delselected/delselected.service.js"></script> |
192 | <script src="partials/pricingcontact/pricingcontact.service.js"></script> | 195 | <script src="partials/pricingcontact/pricingcontact.service.js"></script> |
193 | <script src="partials/viewcontact/viewcontact.service.js"></script> | 196 | <script src="partials/viewcontact/viewcontact.service.js"></script> |
194 | <script src="partials/viewFuelVendor/viewFuelVendor.service.js"></script> | 197 | <script src="partials/viewFuelVendor/viewFuelVendor.service.js"></script> |
195 | <script src="partials/accountSetting/accountSetting.Service.js"></script> | 198 | <script src="partials/accountSetting/accountSetting.Service.js"></script> |
196 | <script src="partials/viewVendorContact/viewVendorContact.Service.js"></script> | 199 | <script src="partials/viewVendorContact/viewVendorContact.Service.js"></script> |
197 | <script src="partials/enterFuelOrder/enterFuelOrder.service.js"></script> | 200 | <script src="partials/enterFuelOrder/enterFuelOrder.service.js"></script> |
198 | 201 | ||
199 | <script src="partials/main/main.service.js"></script> | 202 | <script src="partials/main/main.service.js"></script> |
200 | <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/fullcalendar.min.js'></script> | 203 | <!-- <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/fullcalendar.min.js'></script> --> |
204 | <script type="text/javascript" src="bower_components/fullcalendar/dist/fullcalendar.min.js"></script> | ||
205 | <script type="text/javascript" src="bower_components/fullcalendar/dist/gcal.js"></script> | ||
201 | 206 | ||
202 | </body> | 207 | </body> |
203 | </html> | 208 | </html> |
app/js/app.js
1 | 'use strict'; | 1 | 'use strict'; |
2 | 2 | ||
3 | 3 | ||
4 | angular.module('acufuel', ['ngCookies', 'ngResource', 'ui.router', 'ngAnimate', 'ui.bootstrap', 'xeditable', 'ui.toggle', 'ngTable', 'ui.select2', 'ckeditor']) | 4 | angular.module('acufuel', ['ngCookies', 'ngResource', 'ui.router', 'ngAnimate', 'ui.bootstrap', 'xeditable', 'ui.toggle', 'ngTable', 'ui.select2', 'ckeditor', 'ui.calendar', 'ngDragDrop']) |
5 | 5 | ||
6 | .config(['$httpProvider', function($httpProvider) { | 6 | .config(['$httpProvider', function($httpProvider) { |
7 | $httpProvider.defaults.withCredentials = true; | 7 | $httpProvider.defaults.withCredentials = true; |
8 | $httpProvider.interceptors.push('myCSRF'); | 8 | $httpProvider.interceptors.push('myCSRF'); |
9 | $httpProvider.interceptors.push('httpRequestInterceptor'); | 9 | $httpProvider.interceptors.push('httpRequestInterceptor'); |
10 | }]) | 10 | }]) |
11 | 11 | ||
12 | 12 | ||
13 | .factory('httpRequestInterceptor', ['$q', '$rootScope', '$location', function($q, $rootScope, $location) { | 13 | .factory('httpRequestInterceptor', ['$q', '$rootScope', '$location', function($q, $rootScope, $location) { |
14 | return { | 14 | return { |
15 | request: function($config) { | 15 | request: function($config) { |
16 | return $config; | 16 | return $config; |
17 | }, | 17 | }, |
18 | responseError: function(rejection) { | 18 | responseError: function(rejection) { |
19 | if (rejection.status === 401) { | 19 | if (rejection.status === 401) { |
20 | if($location.path() != "/login"){ | 20 | if($location.path() != "/login"){ |
21 | localStorage.clear(); | 21 | localStorage.clear(); |
22 | window.location.reload(); | 22 | window.location.reload(); |
23 | } | 23 | } |
24 | } | 24 | } |
25 | return $q.reject(rejection); | 25 | return $q.reject(rejection); |
26 | } | 26 | } |
27 | } | 27 | } |
28 | }]) | 28 | }]) |
29 | 29 | ||
30 | .provider('myCSRF',[function(){ | 30 | .provider('myCSRF',[function(){ |
31 | var headerName = 'X-CSRFToken'; | 31 | var headerName = 'X-CSRFToken'; |
32 | var cookieName = 'csrftoken'; | 32 | var cookieName = 'csrftoken'; |
33 | var allowedMethods = ['GET']; | 33 | var allowedMethods = ['GET']; |
34 | 34 | ||
35 | this.setHeaderName = function(n) { | 35 | this.setHeaderName = function(n) { |
36 | headerName = n; | 36 | headerName = n; |
37 | } | 37 | } |
38 | this.setCookieName = function(n) { | 38 | this.setCookieName = function(n) { |
39 | cookieName = n; | 39 | cookieName = n; |
40 | } | 40 | } |
41 | this.setAllowedMethods = function(n) { | 41 | this.setAllowedMethods = function(n) { |
42 | allowedMethods = n; | 42 | allowedMethods = n; |
43 | } | 43 | } |
44 | this.$get = ['$cookies', function($cookies){ | 44 | this.$get = ['$cookies', function($cookies){ |
45 | return { | 45 | return { |
46 | 'request': function(config) { | 46 | 'request': function(config) { |
47 | if(allowedMethods.indexOf(config.method) === -1) { | 47 | if(allowedMethods.indexOf(config.method) === -1) { |
48 | // do something on success | 48 | // do something on success |
49 | config.headers[headerName] = $cookies[cookieName]; | 49 | config.headers[headerName] = $cookies[cookieName]; |
50 | } | 50 | } |
51 | return config; | 51 | return config; |
52 | } | 52 | } |
53 | } | 53 | } |
54 | }]; | 54 | }]; |
55 | }]) | 55 | }]) |
56 | 56 | ||
57 | .config( | 57 | .config( |
58 | ['$locationProvider', '$stateProvider', '$urlRouterProvider', | 58 | ['$locationProvider', '$stateProvider', '$urlRouterProvider', |
59 | function($locationProvider, $stateProvider, $urlRouterProvider) { | 59 | function($locationProvider, $stateProvider, $urlRouterProvider) { |
60 | $locationProvider.hashPrefix('!'); | 60 | $locationProvider.hashPrefix('!'); |
61 | // routes | 61 | // routes |
62 | $urlRouterProvider | 62 | $urlRouterProvider |
63 | .otherwise('/login'); | 63 | .otherwise('/login'); |
64 | 64 | ||
65 | $stateProvider | 65 | $stateProvider |
66 | 66 | ||
67 | .state("app", { | 67 | .state("app", { |
68 | url: "", | 68 | url: "", |
69 | templateUrl: "partials/main/main.html", | 69 | templateUrl: "partials/main/main.html", |
70 | controller: "MainController", | 70 | controller: "MainController", |
71 | abstract: true | 71 | abstract: true |
72 | }) | 72 | }) |
73 | 73 | ||
74 | .state("login", { | 74 | .state("login", { |
75 | url: "/login", | 75 | url: "/login", |
76 | templateUrl: "partials/login/login.html", | 76 | templateUrl: "partials/login/login.html", |
77 | controller: "LoginController" | 77 | controller: "LoginController" |
78 | }) | 78 | }) |
79 | 79 | ||
80 | .state("app.customers", { | 80 | .state("app.customers", { |
81 | url: "/customers", | 81 | url: "/customers", |
82 | templateUrl: "partials/customers/customers.html", | 82 | templateUrl: "partials/customers/customers.html", |
83 | controller: "customersController" | 83 | controller: "customersController" |
84 | }) | 84 | }) |
85 | 85 | ||
86 | .state("app.accountSetting", { | 86 | .state("app.accountSetting", { |
87 | url: "/accountSetting", | 87 | url: "/accountSetting", |
88 | templateUrl: "partials/accountSetting/accountSetting.html", | 88 | templateUrl: "partials/accountSetting/accountSetting.html", |
89 | controller: "AccountSettingController" | 89 | controller: "AccountSettingController" |
90 | }) | 90 | }) |
91 | 91 | ||
92 | .state("app.ContactView", { | 92 | .state("app.ContactView", { |
93 | url: "/ContactView", | 93 | url: "/ContactView", |
94 | templateUrl: "partials/ContactView/ContactView.html", | 94 | templateUrl: "partials/ContactView/ContactView.html", |
95 | controller: "ContactViewController" | 95 | controller: "ContactViewController" |
96 | }) | 96 | }) |
97 | .state("app.FuelVendors", { | 97 | .state("app.FuelVendors", { |
98 | url: "/FuelVendors", | 98 | url: "/FuelVendors", |
99 | templateUrl: "partials/FuelVendors/FuelVendors.html", | 99 | templateUrl: "partials/FuelVendors/FuelVendors.html", |
100 | controller: "FuelVendorsController" | 100 | controller: "FuelVendorsController" |
101 | }) | 101 | }) |
102 | 102 | ||
103 | .state("app.analytics", { | 103 | .state("app.analytics", { |
104 | url: "/analytics", | 104 | url: "/analytics", |
105 | templateUrl: "partials/analytics/analytics.html", | 105 | templateUrl: "partials/analytics/analytics.html", |
106 | controller: "analyticsController" | 106 | controller: "analyticsController" |
107 | }) | 107 | }) |
108 | 108 | ||
109 | .state("app.account", { | 109 | .state("app.account", { |
110 | url: "/account", | 110 | url: "/account", |
111 | templateUrl: "partials/account/account.html", | 111 | templateUrl: "partials/account/account.html", |
112 | controller: "accountController" | 112 | controller: "accountController" |
113 | }) | 113 | }) |
114 | 114 | ||
115 | .state("app.dashboard", { | 115 | .state("app.dashboard", { |
116 | url: "/dashboard", | 116 | url: "/dashboard", |
117 | templateUrl: "partials/dashboard/dashboard.html", | 117 | templateUrl: "partials/dashboard/dashboard.html", |
118 | controller: "dashboardController" | 118 | controller: "dashboardController" |
119 | }) | 119 | }) |
120 | 120 | ||
121 | .state("app.elements", { | 121 | .state("app.elements", { |
122 | url: "/elements", | 122 | url: "/elements", |
123 | templateUrl: "partials/elements/elements.html", | 123 | templateUrl: "partials/elements/elements.html", |
124 | controller: "elementsController" | 124 | controller: "elementsController" |
125 | }) | 125 | }) |
126 | 126 | ||
127 | .state("app.error", { | 127 | .state("app.error", { |
128 | url: "/error", | 128 | url: "/error", |
129 | templateUrl: "partials/error/error.html", | 129 | templateUrl: "partials/error/error.html", |
130 | controller: "errorController" | 130 | controller: "errorController" |
131 | }) | 131 | }) |
132 | 132 | ||
133 | .state("app.faq", { | 133 | .state("app.faq", { |
134 | url: "/faq", | 134 | url: "/faq", |
135 | templateUrl: "partials/faq/faq.html", | 135 | templateUrl: "partials/faq/faq.html", |
136 | controller: "faqController" | 136 | controller: "faqController" |
137 | }) | 137 | }) |
138 | 138 | ||
139 | .state("app.forms", { | 139 | .state("app.forms", { |
140 | url: "/forms", | 140 | url: "/forms", |
141 | templateUrl: "partials/forms/forms.html", | 141 | templateUrl: "partials/forms/forms.html", |
142 | controller: "formsController" | 142 | controller: "formsController" |
143 | }) | 143 | }) |
144 | 144 | ||
145 | .state("app.fuelManager", { | 145 | .state("app.fuelManager", { |
146 | url: "/fuelManager", | 146 | url: "/fuelManager", |
147 | templateUrl: "partials/fuelManager/fuelManager.html", | 147 | templateUrl: "partials/fuelManager/fuelManager.html", |
148 | controller: "fuelManagerController" | 148 | controller: "fuelManagerController" |
149 | }) | 149 | }) |
150 | 150 | ||
151 | .state("app.pricing", { | 151 | .state("app.pricing", { |
152 | url: "/pricing", | 152 | url: "/pricing", |
153 | templateUrl: "partials/pricing/pricing.html", | 153 | templateUrl: "partials/pricing/pricing.html", |
154 | controller: "pricingController" | 154 | controller: "pricingController" |
155 | }) | 155 | }) |
156 | 156 | ||
157 | .state("app.reports", { | 157 | .state("app.reports", { |
158 | url: "/reports", | 158 | url: "/reports", |
159 | templateUrl: "partials/reports/reports.html", | 159 | templateUrl: "partials/reports/reports.html", |
160 | controller: "reportsController" | 160 | controller: "reportsController" |
161 | }) | 161 | }) |
162 | 162 | ||
163 | .state("app.scheduler", { | 163 | .state("app.scheduler", { |
164 | url: "/scheduler", | 164 | url: "/scheduler", |
165 | templateUrl: "partials/scheduler/scheduler.html", | 165 | templateUrl: "partials/scheduler/scheduler.html", |
166 | controller: "schedulerController" | 166 | controller: "schedulerController" |
167 | }) | 167 | }) |
168 | .state("app.signup", { | 168 | .state("app.signup", { |
169 | url: "/signup", | 169 | url: "/signup", |
170 | templateUrl: "partials/signup/signup.html", | 170 | templateUrl: "partials/signup/signup.html", |
171 | controller: "signupController" | 171 | controller: "signupController" |
172 | }) | 172 | }) |
173 | 173 | ||
174 | .state("app.updateFuelManager", { | 174 | .state("app.updateFuelManager", { |
175 | url: "/updateFuelManager", | 175 | url: "/updateFuelManager", |
176 | templateUrl: "partials/updateFuelManager/updateFuelManager.html", | 176 | templateUrl: "partials/updateFuelManager/updateFuelManager.html", |
177 | controller: "updateFuelManagerController", | 177 | controller: "updateFuelManagerController", |
178 | // data: { | 178 | // data: { |
179 | // authorizedRoles: ["fbo"], | 179 | // authorizedRoles: ["fbo"], |
180 | // } | 180 | // } |
181 | }) | 181 | }) |
182 | 182 | ||
183 | .state("app.viewCompany", { | 183 | .state("app.viewCompany", { |
184 | url: "/viewCompany/:id", | 184 | url: "/viewCompany/:id", |
185 | templateUrl: "partials/viewCompany/viewCompany.html", | 185 | templateUrl: "partials/viewCompany/viewCompany.html", |
186 | controller: "viewCompanyController" | 186 | controller: "viewCompanyController" |
187 | }) | 187 | }) |
188 | 188 | ||
189 | .state("app.viewFuelVendor", { | 189 | .state("app.viewFuelVendor", { |
190 | url: "/viewFuelVendor/:id", | 190 | url: "/viewFuelVendor/:id", |
191 | templateUrl: "partials/viewFuelVendor/viewFuelVendor.html", | 191 | templateUrl: "partials/viewFuelVendor/viewFuelVendor.html", |
192 | controller: "ViewFuelVendorController" | 192 | controller: "ViewFuelVendorController" |
193 | }) | 193 | }) |
194 | 194 | ||
195 | .state("app.fuelOrders", { | 195 | .state("app.fuelOrders", { |
196 | url: "/fuelOrders", | 196 | url: "/fuelOrders", |
197 | templateUrl: "partials/fuelOrders/fuelOrders.html", | 197 | templateUrl: "partials/fuelOrders/fuelOrders.html", |
198 | controller: "fuelOrdersController" | 198 | controller: "fuelOrdersController" |
199 | }) | 199 | }) |
200 | 200 | ||
201 | .state("app.DispatchFuel", { | 201 | .state("app.DispatchFuel", { |
202 | url: "/DispatchFuel", | 202 | url: "/DispatchFuel", |
203 | templateUrl: "partials/DispatchFuel/DispatchFuel.html", | 203 | templateUrl: "partials/DispatchFuel/DispatchFuel.html", |
204 | controller: "DispatchFuelController" | 204 | controller: "DispatchFuelController" |
205 | }) | 205 | }) |
206 | 206 | ||
207 | .state("app.searchDispatchFuel", { | 207 | .state("app.searchDispatchFuel", { |
208 | url: "/searchDispatchFuel", | 208 | url: "/searchDispatchFuel", |
209 | templateUrl: "partials/searchDispatchFuel/searchDispatchFuel.html", | 209 | templateUrl: "partials/searchDispatchFuel/searchDispatchFuel.html", |
210 | controller: "searchDispatchFuelController" | 210 | controller: "searchDispatchFuelController" |
211 | }) | 211 | }) |
212 | 212 | ||
213 | .state("app.Accept", { | 213 | .state("app.Accept", { |
214 | url: "/Accept", | 214 | url: "/Accept", |
215 | templateUrl: "partials/Accept/Accept.html", | 215 | templateUrl: "partials/Accept/Accept.html", |
216 | controller: "AcceptController" | 216 | controller: "AcceptController" |
217 | }) | 217 | }) |
218 | 218 | ||
219 | .state("app.delselected", { | 219 | .state("app.delselected", { |
220 | url: "/delselected", | 220 | url: "/delselected", |
221 | templateUrl: "partials/delselected/delselected.html", | 221 | templateUrl: "partials/delselected/delselected.html", |
222 | controller: "delselectedController" | 222 | controller: "delselectedController" |
223 | }) | 223 | }) |
224 | 224 | ||
225 | .state("app.pricingcontact", { | 225 | .state("app.pricingcontact", { |
226 | url: "/pricingcontact", | 226 | url: "/pricingcontact", |
227 | templateUrl: "partials/pricingcontact/pricingcontact.html", | 227 | templateUrl: "partials/pricingcontact/pricingcontact.html", |
228 | controller: "pricingcontactController" | 228 | controller: "pricingcontactController" |
229 | }) | 229 | }) |
230 | 230 | ||
231 | .state("app.viewContact", { | 231 | .state("app.viewContact", { |
232 | url: "/viewContact/:id", | 232 | url: "/viewContact/:id", |
233 | templateUrl: "partials/viewcontact/viewcontact.html", | 233 | templateUrl: "partials/viewcontact/viewcontact.html", |
234 | controller: "viewcontactController" | 234 | controller: "viewcontactController" |
235 | }) | 235 | }) |
236 | 236 | ||
237 | .state("app.viewVendorContact", { | 237 | .state("app.viewVendorContact", { |
238 | url: "/viewVendorContact/:id", | 238 | url: "/viewVendorContact/:id", |
239 | templateUrl: "partials/viewVendorContact/viewVendorContact.html", | 239 | templateUrl: "partials/viewVendorContact/viewVendorContact.html", |
240 | controller: "viewVendorContactController" | 240 | controller: "viewVendorContactController" |
241 | }) | 241 | }) |
242 | 242 | ||
243 | .state("app.enterFuelOrder", { | 243 | .state("app.enterFuelOrder", { |
244 | url: "/enterFuelOrder", | 244 | url: "/enterFuelOrder", |
245 | templateUrl: "partials/enterFuelOrder/enterFuelOrder.html", | 245 | templateUrl: "partials/enterFuelOrder/enterFuelOrder.html", |
246 | controller: "enterFuelOrderController" | 246 | controller: "enterFuelOrderController" |
247 | }) | 247 | }) |
248 | 248 | ||
249 | } | 249 | } |
250 | ]) | 250 | ]) |
251 | 251 | ||
252 | .run(['$rootScope', '$state', 'LoginService', 'AUTH_EVENTS', function($rootScope, $state, LoginService, AUTH_EVENTS) { | 252 | .run(['$rootScope', '$state', 'LoginService', 'AUTH_EVENTS', function($rootScope, $state, LoginService, AUTH_EVENTS) { |
253 | $rootScope.$on('$stateChangeStart', function (event, next, nextParams, fromState) { | 253 | $rootScope.$on('$stateChangeStart', function (event, next, nextParams, fromState) { |
254 | $rootScope.currentUser = JSON.parse(window.localStorage.getItem('currentUser')); | 254 | $rootScope.currentUser = JSON.parse(window.localStorage.getItem('currentUser')); |
255 | 255 | ||
256 | LoginService.isAuthorized = function (authorizedRoles) { | 256 | LoginService.isAuthorized = function (authorizedRoles) { |
257 | if (!angular.isArray(authorizedRoles)) { | 257 | if (!angular.isArray(authorizedRoles)) { |
258 | authorizedRoles = [authorizedRoles]; | 258 | authorizedRoles = [authorizedRoles]; |
259 | } | 259 | } |
260 | var userdata = JSON.parse(window.localStorage.getItem('currentUser')); | 260 | var userdata = JSON.parse(window.localStorage.getItem('currentUser')); |
261 | return (userdata? (authorizedRoles.indexOf(userdata.type) !== -1): false); | 261 | return (userdata? (authorizedRoles.indexOf(userdata.type) !== -1): false); |
262 | } | 262 | } |
263 | 263 | ||
264 | if ('data' in next && 'authorizedRoles' in next.data) { | 264 | if ('data' in next && 'authorizedRoles' in next.data) { |
265 | var authorizedRoles = next.data.authorizedRoles; | 265 | var authorizedRoles = next.data.authorizedRoles; |
266 | if (!LoginService.isAuthorized(authorizedRoles)) { | 266 | if (!LoginService.isAuthorized(authorizedRoles)) { |
267 | event.preventDefault(); | 267 | event.preventDefault(); |
268 | if($state.current.name.length == 0) { | 268 | if($state.current.name.length == 0) { |
269 | $state.go('login') | 269 | $state.go('login') |
270 | } else { | 270 | } else { |
271 | $state.go($state.current, {}, {reload: true}); | 271 | $state.go($state.current, {}, {reload: true}); |
272 | $rootScope.$broadcast(AUTH_EVENTS.notAuthorized); | 272 | $rootScope.$broadcast(AUTH_EVENTS.notAuthorized); |
273 | } | 273 | } |
274 | } | 274 | } |
275 | } | 275 | } |
276 | 276 | ||
277 | if (LoginService.isAuthenticated()) { | 277 | if (LoginService.isAuthenticated()) { |
278 | if (next.name == 'login') { | 278 | if (next.name == 'login') { |
279 | event.preventDefault(); | 279 | event.preventDefault(); |
280 | $state.go('app.dashboard'); | 280 | $state.go('app.dashboard'); |
281 | } | 281 | } |
282 | } | 282 | } |
283 | }); | 283 | }); |
284 | }]) | 284 | }]) |
285 | 285 | ||
286 | 286 | ||
287 | .directive("datepicker",function(){ | 287 | .directive("datepicker",function(){ |
288 | return { | 288 | return { |
289 | restrict:"A", | 289 | restrict:"A", |
290 | link:function(scope,el,attr){ | 290 | link:function(scope,el,attr){ |
291 | el.datepicker(); | 291 | el.datepicker(); |
292 | } | 292 | } |
293 | }; | 293 | }; |
294 | }) | 294 | }) |
295 | 295 | ||
296 | 296 | ||
297 | 297 | ||
298 | 298 | ||
299 | 299 | ||
300 | 300 | ||
301 | 301 |
app/js/fullcalender.js
1 | /*! | ||
2 | 2 | * FullCalendar v2.3.1 | |
3 | 3 | * Docs & License: http://fullcalendar.io/ | |
4 | 4 | * (c) 2015 Adam Shaw | |
5 | */ | ||
6 | |||
7 | |||
8 | |||
9 | |||
10 | |||
6 | |||
7 | (function(factory) { | ||
8 | if (typeof define === 'function' && define.amd) { | ||
9 | define([ 'jquery', 'moment' ], factory); | ||
10 | } | ||
11 | else if (typeof exports === 'object') { // Node/CommonJS | ||
12 | module.exports = factory(require('jquery'), require('moment')); | ||
13 | } | ||
14 | else { | ||
15 | factory(jQuery, moment); | ||
16 | } | ||
17 | })(function($, moment) { | ||
18 | |||
19 | ;; | ||
20 | |||
21 | var fc = $.fullCalendar = { version: "2.3.1" }; | ||
22 | var fcViews = fc.views = {}; | ||
23 | |||
24 | |||
25 | $.fn.fullCalendar = function(options) { | ||
26 | var args = Array.prototype.slice.call(arguments, 1); // for a possible method call | ||
27 | var res = this; // what this function will return (this jQuery object by default) | ||
28 | |||
29 | this.each(function(i, _element) { // loop each DOM element involved | ||
30 | var element = $(_element); | ||
31 | var calendar = element.data('fullCalendar'); // get the existing calendar object (if any) | ||
32 | var singleRes; // the returned value of this single method call | ||
33 | |||
34 | // a method call | ||
35 | if (typeof options === 'string') { | ||
36 | if (calendar && $.isFunction(calendar[options])) { | ||
37 | singleRes = calendar[options].apply(calendar, args); | ||
38 | if (!i) { | ||
39 | res = singleRes; // record the first method call result | ||
40 | } | ||
41 | if (options === 'destroy') { // for the destroy method, must remove Calendar object data | ||
42 | element.removeData('fullCalendar'); | ||
43 | } | ||
44 | } | ||
45 | } | ||
46 | // a new calendar initialization | ||
47 | else if (!calendar) { // don't initialize twice | ||
48 | calendar = new fc.CalendarBase(element, options); | ||
49 | element.data('fullCalendar', calendar); | ||
50 | calendar.render(); | ||
51 | } | ||
52 | }); | ||
53 | |||
54 | return res; | ||
55 | }; | ||
56 | |||
57 | |||
58 | var complexOptions = [ // names of options that are objects whose properties should be combined | ||
59 | 'header', | ||
60 | 'buttonText', | ||
61 | 'buttonIcons', | ||
62 | 'themeButtonIcons' | ||
63 | ]; | ||
64 | |||
65 | |||
66 | // Recursively combines all passed-in option-hash arguments into a new single option-hash. | ||
67 | // Given option-hashes are ordered from lowest to highest priority. | ||
68 | function mergeOptions() { | ||
69 | var chain = Array.prototype.slice.call(arguments); // convert to a real array | ||
70 | var complexVals = {}; // hash for each complex option's combined values | ||
71 | var i, name; | ||
72 | var combinedVal; | ||
73 | var j; | ||
74 | var val; | ||
75 | |||
76 | // for each complex option, loop through each option-hash and accumulate the combined values | ||
77 | for (i = 0; i < complexOptions.length; i++) { | ||
78 | name = complexOptions[i]; | ||
79 | combinedVal = null; // an object holding the merge of all the values | ||
80 | |||
81 | for (j = 0; j < chain.length; j++) { | ||
82 | val = chain[j][name]; | ||
83 | |||
84 | if ($.isPlainObject(val)) { | ||
85 | combinedVal = $.extend(combinedVal || {}, val); // merge new properties | ||
86 | } | ||
87 | else if (val != null) { // a non-null non-undefined atomic option | ||
88 | combinedVal = null; // signal to use the atomic value | ||
89 | } | ||
90 | } | ||
91 | |||
92 | // if not null, the final value was a combination of other objects. record it | ||
93 | if (combinedVal !== null) { | ||
94 | complexVals[name] = combinedVal; | ||
95 | } | ||
96 | } | ||
97 | |||
98 | chain.unshift({}); // $.extend will mutate this with the result | ||
99 | chain.push(complexVals); // computed complex values are applied last | ||
100 | return $.extend.apply($, chain); // combine | ||
101 | } | ||
102 | |||
103 | |||
104 | // Given options specified for the calendar's constructor, massages any legacy options into a non-legacy form. | ||
105 | // Converts View-Option-Hashes into the View-Specific-Options format. | ||
106 | function massageOverrides(input) { | ||
107 | var overrides = { views: input.views || {} }; // the output. ensure a `views` hash | ||
108 | var subObj; | ||
109 | |||
110 | // iterate through all option override properties (except `views`) | ||
111 | $.each(input, function(name, val) { | ||
112 | if (name != 'views') { | ||
113 | |||
114 | // could the value be a legacy View-Option-Hash? | ||
115 | if ( | ||
116 | $.isPlainObject(val) && | ||
117 | !/(time|duration|interval)$/i.test(name) && // exclude duration options. might be given as objects | ||
118 | $.inArray(name, complexOptions) == -1 // complex options aren't allowed to be View-Option-Hashes | ||
119 | ) { | ||
120 | subObj = null; | ||
121 | |||
122 | // iterate through the properties of this possible View-Option-Hash value | ||
123 | $.each(val, function(subName, subVal) { | ||
124 | |||
125 | // is the property targeting a view? | ||
126 | if (/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(subName)) { | ||
127 | if (!overrides.views[subName]) { // ensure the view-target entry exists | ||
128 | overrides.views[subName] = {}; | ||
129 | } | ||
130 | overrides.views[subName][name] = subVal; // record the value in the `views` object | ||
131 | } | ||
132 | else { // a non-View-Option-Hash property | ||
133 | if (!subObj) { | ||
134 | subObj = {}; | ||
135 | } | ||
136 | subObj[subName] = subVal; // accumulate these unrelated values for later | ||
137 | } | ||
138 | }); | ||
139 | |||
140 | if (subObj) { // non-View-Option-Hash properties? transfer them as-is | ||
141 | overrides[name] = subObj; | ||
142 | } | ||
143 | } | ||
144 | else { | ||
145 | overrides[name] = val; // transfer normal options as-is | ||
146 | } | ||
147 | } | ||
148 | }); | ||
149 | |||
150 | return overrides; | ||
151 | } | ||
152 | |||
153 | ;; | ||
154 | |||
155 | // exports | ||
156 | fc.intersectionToSeg = intersectionToSeg; | ||
157 | fc.applyAll = applyAll; | ||
158 | fc.debounce = debounce; | ||
159 | fc.isInt = isInt; | ||
160 | fc.htmlEscape = htmlEscape; | ||
161 | fc.cssToStr = cssToStr; | ||
162 | fc.proxy = proxy; | ||
163 | |||
164 | |||
165 | /* FullCalendar-specific DOM Utilities | ||
166 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
167 | |||
168 | |||
169 | // Given the scrollbar widths of some other container, create borders/margins on rowEls in order to match the left | ||
170 | // and right space that was offset by the scrollbars. A 1-pixel border first, then margin beyond that. | ||
171 | function compensateScroll(rowEls, scrollbarWidths) { | ||
172 | if (scrollbarWidths.left) { | ||
173 | rowEls.css({ | ||
174 | 'border-left-width': 1, | ||
175 | 'margin-left': scrollbarWidths.left - 1 | ||
176 | }); | ||
177 | } | ||
178 | if (scrollbarWidths.right) { | ||
179 | rowEls.css({ | ||
180 | 'border-right-width': 1, | ||
181 | 'margin-right': scrollbarWidths.right - 1 | ||
182 | }); | ||
183 | } | ||
184 | } | ||
185 | |||
186 | |||
187 | // Undoes compensateScroll and restores all borders/margins | ||
188 | function uncompensateScroll(rowEls) { | ||
189 | rowEls.css({ | ||
190 | 'margin-left': '', | ||
191 | 'margin-right': '', | ||
192 | 'border-left-width': '', | ||
193 | 'border-right-width': '' | ||
194 | }); | ||
195 | } | ||
196 | |||
197 | |||
198 | // Make the mouse cursor express that an event is not allowed in the current area | ||
199 | function disableCursor() { | ||
200 | $('body').addClass('fc-not-allowed'); | ||
201 | } | ||
202 | |||
203 | |||
204 | // Returns the mouse cursor to its original look | ||
205 | function enableCursor() { | ||
206 | $('body').removeClass('fc-not-allowed'); | ||
207 | } | ||
208 | |||
209 | |||
210 | // Given a total available height to fill, have `els` (essentially child rows) expand to accomodate. | ||
211 | // By default, all elements that are shorter than the recommended height are expanded uniformly, not considering | ||
212 | // any other els that are already too tall. if `shouldRedistribute` is on, it considers these tall rows and | ||
213 | // reduces the available height. | ||
214 | function distributeHeight(els, availableHeight, shouldRedistribute) { | ||
215 | |||
216 | // *FLOORING NOTE*: we floor in certain places because zoom can give inaccurate floating-point dimensions, | ||
217 | // and it is better to be shorter than taller, to avoid creating unnecessary scrollbars. | ||
218 | |||
219 | var minOffset1 = Math.floor(availableHeight / els.length); // for non-last element | ||
220 | var minOffset2 = Math.floor(availableHeight - minOffset1 * (els.length - 1)); // for last element *FLOORING NOTE* | ||
221 | var flexEls = []; // elements that are allowed to expand. array of DOM nodes | ||
222 | var flexOffsets = []; // amount of vertical space it takes up | ||
223 | var flexHeights = []; // actual css height | ||
224 | var usedHeight = 0; | ||
225 | |||
226 | undistributeHeight(els); // give all elements their natural height | ||
227 | |||
228 | // find elements that are below the recommended height (expandable). | ||
229 | // important to query for heights in a single first pass (to avoid reflow oscillation). | ||
230 | els.each(function(i, el) { | ||
231 | var minOffset = i === els.length - 1 ? minOffset2 : minOffset1; | ||
232 | var naturalOffset = $(el).outerHeight(true); | ||
233 | |||
234 | if (naturalOffset < minOffset) { | ||
235 | flexEls.push(el); | ||
236 | flexOffsets.push(naturalOffset); | ||
237 | flexHeights.push($(el).height()); | ||
238 | } | ||
239 | else { | ||
240 | // this element stretches past recommended height (non-expandable). mark the space as occupied. | ||
241 | usedHeight += naturalOffset; | ||
242 | } | ||
243 | }); | ||
244 | |||
245 | // readjust the recommended height to only consider the height available to non-maxed-out rows. | ||
246 | if (shouldRedistribute) { | ||
247 | availableHeight -= usedHeight; | ||
248 | minOffset1 = Math.floor(availableHeight / flexEls.length); | ||
249 | minOffset2 = Math.floor(availableHeight - minOffset1 * (flexEls.length - 1)); // *FLOORING NOTE* | ||
250 | } | ||
251 | |||
252 | // assign heights to all expandable elements | ||
253 | $(flexEls).each(function(i, el) { | ||
254 | var minOffset = i === flexEls.length - 1 ? minOffset2 : minOffset1; | ||
255 | var naturalOffset = flexOffsets[i]; | ||
256 | var naturalHeight = flexHeights[i]; | ||
257 | var newHeight = minOffset - (naturalOffset - naturalHeight); // subtract the margin/padding | ||
258 | |||
259 | if (naturalOffset < minOffset) { // we check this again because redistribution might have changed things | ||
260 | $(el).height(newHeight); | ||
261 | } | ||
262 | }); | ||
263 | } | ||
264 | |||
265 | |||
266 | // Undoes distrubuteHeight, restoring all els to their natural height | ||
267 | function undistributeHeight(els) { | ||
268 | els.height(''); | ||
269 | } | ||
270 | |||
271 | |||
272 | // Given `els`, a jQuery set of <td> cells, find the cell with the largest natural width and set the widths of all the | ||
273 | // cells to be that width. | ||
274 | // PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline | ||
275 | function matchCellWidths(els) { | ||
276 | var maxInnerWidth = 0; | ||
277 | |||
278 | els.find('> *').each(function(i, innerEl) { | ||
279 | var innerWidth = $(innerEl).outerWidth(); | ||
280 | if (innerWidth > maxInnerWidth) { | ||
281 | maxInnerWidth = innerWidth; | ||
282 | } | ||
283 | }); | ||
284 | |||
285 | maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance | ||
286 | |||
287 | els.width(maxInnerWidth); | ||
288 | |||
289 | return maxInnerWidth; | ||
290 | } | ||
291 | |||
292 | |||
293 | // Turns a container element into a scroller if its contents is taller than the allotted height. | ||
294 | // Returns true if the element is now a scroller, false otherwise. | ||
295 | // NOTE: this method is best because it takes weird zooming dimensions into account | ||
296 | function setPotentialScroller(containerEl, height) { | ||
297 | containerEl.height(height).addClass('fc-scroller'); | ||
298 | |||
299 | // are scrollbars needed? | ||
300 | if (containerEl[0].scrollHeight - 1 > containerEl[0].clientHeight) { // !!! -1 because IE is often off-by-one :( | ||
301 | return true; | ||
302 | } | ||
303 | |||
304 | unsetScroller(containerEl); // undo | ||
305 | return false; | ||
306 | } | ||
307 | |||
308 | |||
309 | // Takes an element that might have been a scroller, and turns it back into a normal element. | ||
310 | function unsetScroller(containerEl) { | ||
311 | containerEl.height('').removeClass('fc-scroller'); | ||
312 | } | ||
313 | |||
314 | |||
315 | /* General DOM Utilities | ||
316 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
317 | |||
318 | fc.getClientRect = getClientRect; | ||
319 | fc.getContentRect = getContentRect; | ||
320 | fc.getScrollbarWidths = getScrollbarWidths; | ||
321 | |||
322 | |||
323 | // borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51 | ||
324 | function getScrollParent(el) { | ||
325 | var position = el.css('position'), | ||
326 | scrollParent = el.parents().filter(function() { | ||
327 | var parent = $(this); | ||
328 | return (/(auto|scroll)/).test( | ||
329 | parent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x') | ||
330 | ); | ||
331 | }).eq(0); | ||
332 | |||
333 | return position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent; | ||
334 | } | ||
335 | |||
336 | |||
337 | // Queries the outer bounding area of a jQuery element. | ||
338 | // Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive). | ||
339 | function getOuterRect(el) { | ||
340 | var offset = el.offset(); | ||
341 | |||
342 | return { | ||
343 | left: offset.left, | ||
344 | right: offset.left + el.outerWidth(), | ||
345 | top: offset.top, | ||
346 | bottom: offset.top + el.outerHeight() | ||
347 | }; | ||
348 | } | ||
349 | |||
350 | |||
351 | // Queries the area within the margin/border/scrollbars of a jQuery element. Does not go within the padding. | ||
352 | // Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive). | ||
353 | // NOTE: should use clientLeft/clientTop, but very unreliable cross-browser. | ||
354 | function getClientRect(el) { | ||
355 | var offset = el.offset(); | ||
356 | var scrollbarWidths = getScrollbarWidths(el); | ||
357 | var left = offset.left + getCssFloat(el, 'border-left-width') + scrollbarWidths.left; | ||
358 | var top = offset.top + getCssFloat(el, 'border-top-width') + scrollbarWidths.top; | ||
359 | |||
360 | return { | ||
361 | left: left, | ||
362 | right: left + el[0].clientWidth, // clientWidth includes padding but NOT scrollbars | ||
363 | top: top, | ||
364 | bottom: top + el[0].clientHeight // clientHeight includes padding but NOT scrollbars | ||
365 | }; | ||
366 | } | ||
367 | |||
368 | |||
369 | // Queries the area within the margin/border/padding of a jQuery element. Assumed not to have scrollbars. | ||
370 | // Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive). | ||
371 | function getContentRect(el) { | ||
372 | var offset = el.offset(); // just outside of border, margin not included | ||
373 | var left = offset.left + getCssFloat(el, 'border-left-width') + getCssFloat(el, 'padding-left'); | ||
374 | var top = offset.top + getCssFloat(el, 'border-top-width') + getCssFloat(el, 'padding-top'); | ||
375 | |||
376 | return { | ||
377 | left: left, | ||
378 | right: left + el.width(), | ||
379 | top: top, | ||
380 | bottom: top + el.height() | ||
381 | }; | ||
382 | } | ||
383 | |||
384 | |||
385 | // Returns the computed left/right/top/bottom scrollbar widths for the given jQuery element. | ||
386 | // NOTE: should use clientLeft/clientTop, but very unreliable cross-browser. | ||
387 | function getScrollbarWidths(el) { | ||
388 | var leftRightWidth = el.innerWidth() - el[0].clientWidth; // the paddings cancel out, leaving the scrollbars | ||
389 | var widths = { | ||
390 | left: 0, | ||
391 | right: 0, | ||
392 | top: 0, | ||
393 | bottom: el.innerHeight() - el[0].clientHeight // the paddings cancel out, leaving the bottom scrollbar | ||
394 | }; | ||
395 | |||
396 | if (getIsLeftRtlScrollbars() && el.css('direction') == 'rtl') { // is the scrollbar on the left side? | ||
397 | widths.left = leftRightWidth; | ||
398 | } | ||
399 | else { | ||
400 | widths.right = leftRightWidth; | ||
401 | } | ||
402 | |||
403 | return widths; | ||
404 | } | ||
405 | |||
406 | |||
407 | // Logic for determining if, when the element is right-to-left, the scrollbar appears on the left side | ||
408 | |||
409 | var _isLeftRtlScrollbars = null; | ||
410 | |||
411 | function getIsLeftRtlScrollbars() { // responsible for caching the computation | ||
412 | if (_isLeftRtlScrollbars === null) { | ||
413 | _isLeftRtlScrollbars = computeIsLeftRtlScrollbars(); | ||
414 | } | ||
415 | return _isLeftRtlScrollbars; | ||
416 | } | ||
417 | |||
418 | function computeIsLeftRtlScrollbars() { // creates an offscreen test element, then removes it | ||
419 | var el = $('<div><div/></div>') | ||
420 | .css({ | ||
421 | position: 'absolute', | ||
422 | top: -1000, | ||
423 | left: 0, | ||
424 | border: 0, | ||
425 | padding: 0, | ||
426 | overflow: 'scroll', | ||
427 | direction: 'rtl' | ||
428 | }) | ||
429 | .appendTo('body'); | ||
430 | var innerEl = el.children(); | ||
431 | var res = innerEl.offset().left > el.offset().left; // is the inner div shifted to accommodate a left scrollbar? | ||
432 | el.remove(); | ||
433 | return res; | ||
434 | } | ||
435 | |||
436 | |||
437 | // Retrieves a jQuery element's computed CSS value as a floating-point number. | ||
438 | // If the queried value is non-numeric (ex: IE can return "medium" for border width), will just return zero. | ||
439 | function getCssFloat(el, prop) { | ||
440 | return parseFloat(el.css(prop)) || 0; | ||
441 | } | ||
442 | |||
443 | |||
444 | // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) | ||
445 | function isPrimaryMouseButton(ev) { | ||
446 | return ev.which == 1 && !ev.ctrlKey; | ||
447 | } | ||
448 | |||
449 | |||
450 | /* Geometry | ||
451 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
452 | |||
453 | |||
454 | // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false | ||
455 | function intersectRects(rect1, rect2) { | ||
456 | var res = { | ||
457 | left: Math.max(rect1.left, rect2.left), | ||
458 | right: Math.min(rect1.right, rect2.right), | ||
459 | top: Math.max(rect1.top, rect2.top), | ||
460 | bottom: Math.min(rect1.bottom, rect2.bottom) | ||
461 | }; | ||
462 | |||
463 | if (res.left < res.right && res.top < res.bottom) { | ||
464 | return res; | ||
465 | } | ||
466 | return false; | ||
467 | } | ||
468 | |||
469 | |||
470 | // Returns a new point that will have been moved to reside within the given rectangle | ||
471 | function constrainPoint(point, rect) { | ||
472 | return { | ||
473 | left: Math.min(Math.max(point.left, rect.left), rect.right), | ||
474 | top: Math.min(Math.max(point.top, rect.top), rect.bottom) | ||
475 | }; | ||
476 | } | ||
477 | |||
478 | |||
479 | // Returns a point that is the center of the given rectangle | ||
480 | function getRectCenter(rect) { | ||
481 | return { | ||
482 | left: (rect.left + rect.right) / 2, | ||
483 | top: (rect.top + rect.bottom) / 2 | ||
484 | }; | ||
485 | } | ||
486 | |||
487 | |||
488 | // Subtracts point2's coordinates from point1's coordinates, returning a delta | ||
489 | function diffPoints(point1, point2) { | ||
490 | return { | ||
491 | left: point1.left - point2.left, | ||
492 | top: point1.top - point2.top | ||
493 | }; | ||
494 | } | ||
495 | |||
496 | |||
497 | /* FullCalendar-specific Misc Utilities | ||
498 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
499 | |||
500 | |||
501 | // Creates a basic segment with the intersection of the two ranges. Returns undefined if no intersection. | ||
502 | // Expects all dates to be normalized to the same timezone beforehand. | ||
503 | // TODO: move to date section? | ||
504 | function intersectionToSeg(subjectRange, constraintRange) { | ||
505 | var subjectStart = subjectRange.start; | ||
506 | var subjectEnd = subjectRange.end; | ||
507 | var constraintStart = constraintRange.start; | ||
508 | var constraintEnd = constraintRange.end; | ||
509 | var segStart, segEnd; | ||
510 | var isStart, isEnd; | ||
511 | |||
512 | if (subjectEnd > constraintStart && subjectStart < constraintEnd) { // in bounds at all? | ||
513 | |||
514 | if (subjectStart >= constraintStart) { | ||
515 | segStart = subjectStart.clone(); | ||
516 | isStart = true; | ||
517 | } | ||
518 | else { | ||
519 | segStart = constraintStart.clone(); | ||
520 | isStart = false; | ||
521 | } | ||
522 | |||
523 | if (subjectEnd <= constraintEnd) { | ||
524 | segEnd = subjectEnd.clone(); | ||
525 | isEnd = true; | ||
526 | } | ||
527 | else { | ||
528 | segEnd = constraintEnd.clone(); | ||
529 | isEnd = false; | ||
530 | } | ||
531 | |||
532 | return { | ||
533 | start: segStart, | ||
534 | end: segEnd, | ||
535 | isStart: isStart, | ||
536 | isEnd: isEnd | ||
537 | }; | ||
538 | } | ||
539 | } | ||
540 | |||
541 | |||
542 | /* Date Utilities | ||
543 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
544 | |||
545 | fc.computeIntervalUnit = computeIntervalUnit; | ||
546 | fc.durationHasTime = durationHasTime; | ||
547 | |||
548 | var dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ]; | ||
549 | var intervalUnits = [ 'year', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond' ]; | ||
550 | |||
551 | |||
552 | // Diffs the two moments into a Duration where full-days are recorded first, then the remaining time. | ||
553 | // Moments will have their timezones normalized. | ||
554 | function diffDayTime(a, b) { | ||
555 | return moment.duration({ | ||
556 | days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'), | ||
557 | ms: a.time() - b.time() // time-of-day from day start. disregards timezone | ||
558 | }); | ||
559 | } | ||
560 | |||
561 | |||
562 | // Diffs the two moments via their start-of-day (regardless of timezone). Produces whole-day durations. | ||
563 | function diffDay(a, b) { | ||
564 | return moment.duration({ | ||
565 | days: a.clone().stripTime().diff(b.clone().stripTime(), 'days') | ||
566 | }); | ||
567 | } | ||
568 | |||
569 | |||
570 | // Diffs two moments, producing a duration, made of a whole-unit-increment of the given unit. Uses rounding. | ||
571 | function diffByUnit(a, b, unit) { | ||
572 | return moment.duration( | ||
573 | Math.round(a.diff(b, unit, true)), // returnFloat=true | ||
574 | unit | ||
575 | ); | ||
576 | } | ||
577 | |||
578 | |||
579 | // Computes the unit name of the largest whole-unit period of time. | ||
580 | // For example, 48 hours will be "days" whereas 49 hours will be "hours". | ||
581 | // Accepts start/end, a range object, or an original duration object. | ||
582 | function computeIntervalUnit(start, end) { | ||
583 | var i, unit; | ||
584 | var val; | ||
585 | |||
586 | for (i = 0; i < intervalUnits.length; i++) { | ||
587 | unit = intervalUnits[i]; | ||
588 | val = computeRangeAs(unit, start, end); | ||
589 | |||
590 | if (val >= 1 && isInt(val)) { | ||
591 | break; | ||
592 | } | ||
593 | } | ||
594 | |||
595 | return unit; // will be "milliseconds" if nothing else matches | ||
596 | } | ||
597 | |||
598 | |||
599 | // Computes the number of units (like "hours") in the given range. | ||
600 | // Range can be a {start,end} object, separate start/end args, or a Duration. | ||
601 | // Results are based on Moment's .as() and .diff() methods, so results can depend on internal handling | ||
602 | // of month-diffing logic (which tends to vary from version to version). | ||
603 | function computeRangeAs(unit, start, end) { | ||
604 | |||
605 | if (end != null) { // given start, end | ||
606 | return end.diff(start, unit, true); | ||
607 | } | ||
608 | else if (moment.isDuration(start)) { // given duration | ||
609 | return start.as(unit); | ||
610 | } | ||
611 | else { // given { start, end } range object | ||
612 | return start.end.diff(start.start, unit, true); | ||
613 | } | ||
614 | } | ||
615 | |||
616 | |||
617 | // Returns a boolean about whether the given duration has any time parts (hours/minutes/seconds/ms) | ||
618 | function durationHasTime(dur) { | ||
619 | return Boolean(dur.hours() || dur.minutes() || dur.seconds() || dur.milliseconds()); | ||
620 | } | ||
621 | |||
622 | |||
623 | function isNativeDate(input) { | ||
624 | return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date; | ||
625 | } | ||
626 | |||
627 | |||
628 | // Returns a boolean about whether the given input is a time string, like "06:40:00" or "06:00" | ||
629 | function isTimeString(str) { | ||
630 | return /^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(str); | ||
631 | } | ||
632 | |||
633 | |||
634 | /* General Utilities | ||
635 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
636 | |||
637 | var hasOwnPropMethod = {}.hasOwnProperty; | ||
638 | |||
639 | |||
640 | // Create an object that has the given prototype. Just like Object.create | ||
641 | function createObject(proto) { | ||
642 | var f = function() {}; | ||
643 | f.prototype = proto; | ||
644 | return new f(); | ||
645 | } | ||
646 | |||
647 | |||
648 | function copyOwnProps(src, dest) { | ||
649 | for (var name in src) { | ||
650 | if (hasOwnProp(src, name)) { | ||
651 | dest[name] = src[name]; | ||
652 | } | ||
653 | } | ||
654 | } | ||
655 | |||
656 | |||
657 | // Copies over certain methods with the same names as Object.prototype methods. Overcomes an IE<=8 bug: | ||
658 | // https://developer.mozilla.org/en-US/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug | ||
659 | function copyNativeMethods(src, dest) { | ||
660 | var names = [ 'constructor', 'toString', 'valueOf' ]; | ||
661 | var i, name; | ||
662 | |||
663 | for (i = 0; i < names.length; i++) { | ||
664 | name = names[i]; | ||
665 | |||
666 | if (src[name] !== Object.prototype[name]) { | ||
667 | dest[name] = src[name]; | ||
668 | } | ||
669 | } | ||
670 | } | ||
671 | |||
672 | |||
673 | function hasOwnProp(obj, name) { | ||
674 | return hasOwnPropMethod.call(obj, name); | ||
675 | } | ||
676 | |||
677 | |||
678 | // Is the given value a non-object non-function value? | ||
679 | function isAtomic(val) { | ||
680 | return /undefined|null|boolean|number|string/.test($.type(val)); | ||
681 | } | ||
682 | |||
683 | |||
684 | function applyAll(functions, thisObj, args) { | ||
685 | if ($.isFunction(functions)) { | ||
686 | functions = [ functions ]; | ||
687 | } | ||
688 | if (functions) { | ||
689 | var i; | ||
690 | var ret; | ||
691 | for (i=0; i<functions.length; i++) { | ||
692 | ret = functions[i].apply(thisObj, args) || ret; | ||
693 | } | ||
694 | return ret; | ||
695 | } | ||
696 | } | ||
697 | |||
698 | |||
699 | function firstDefined() { | ||
700 | for (var i=0; i<arguments.length; i++) { | ||
701 | if (arguments[i] !== undefined) { | ||
702 | return arguments[i]; | ||
703 | } | ||
704 | } | ||
705 | } | ||
706 | |||
707 | |||
708 | function htmlEscape(s) { | ||
709 | return (s + '').replace(/&/g, '&') | ||
710 | .replace(/</g, '<') | ||
711 | .replace(/>/g, '>') | ||
712 | .replace(/'/g, ''') | ||
713 | .replace(/"/g, '"') | ||
714 | .replace(/\n/g, '<br />'); | ||
715 | } | ||
716 | |||
717 | |||
718 | function stripHtmlEntities(text) { | ||
719 | return text.replace(/&.*?;/g, ''); | ||
720 | } | ||
721 | |||
722 | |||
723 | // Given a hash of CSS properties, returns a string of CSS. | ||
724 | // Uses property names as-is (no camel-case conversion). Will not make statements for null/undefined values. | ||
725 | function cssToStr(cssProps) { | ||
726 | var statements = []; | ||
727 | |||
728 | $.each(cssProps, function(name, val) { | ||
729 | if (val != null) { | ||
730 | statements.push(name + ':' + val); | ||
731 | } | ||
732 | }); | ||
733 | |||
734 | return statements.join(';'); | ||
735 | } | ||
736 | |||
737 | |||
738 | function capitaliseFirstLetter(str) { | ||
739 | return str.charAt(0).toUpperCase() + str.slice(1); | ||
740 | } | ||
741 | |||
742 | |||
743 | function compareNumbers(a, b) { // for .sort() | ||
744 | return a - b; | ||
745 | } | ||
746 | |||
747 | |||
748 | function isInt(n) { | ||
749 | return n % 1 === 0; | ||
750 | } | ||
751 | |||
752 | |||
753 | // Returns a method bound to the given object context. | ||
754 | // Just like one of the jQuery.proxy signatures, but without the undesired behavior of treating the same method with | ||
755 | // different contexts as identical when binding/unbinding events. | ||
756 | function proxy(obj, methodName) { | ||
757 | var method = obj[methodName]; | ||
758 | |||
759 | return function() { | ||
760 | return method.apply(obj, arguments); | ||
761 | }; | ||
762 | } | ||
763 | |||
764 | |||
765 | // Returns a function, that, as long as it continues to be invoked, will not | ||
766 | // be triggered. The function will be called after it stops being called for | ||
767 | // N milliseconds. | ||
768 | // https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714 | ||
769 | function debounce(func, wait) { | ||
770 | var timeoutId; | ||
771 | var args; | ||
772 | var context; | ||
773 | var timestamp; // of most recent call | ||
774 | var later = function() { | ||
775 | var last = +new Date() - timestamp; | ||
776 | if (last < wait && last > 0) { | ||
777 | timeoutId = setTimeout(later, wait - last); | ||
778 | } | ||
779 | else { | ||
780 | timeoutId = null; | ||
781 | func.apply(context, args); | ||
782 | if (!timeoutId) { | ||
783 | context = args = null; | ||
784 | } | ||
785 | } | ||
786 | }; | ||
787 | |||
788 | return function() { | ||
789 | context = this; | ||
790 | args = arguments; | ||
791 | timestamp = +new Date(); | ||
792 | if (!timeoutId) { | ||
793 | timeoutId = setTimeout(later, wait); | ||
794 | } | ||
795 | }; | ||
796 | } | ||
797 | |||
798 | ;; | ||
799 | |||
800 | var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/; | ||
801 | var ambigTimeOrZoneRegex = | ||
802 | /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/; | ||
803 | var newMomentProto = moment.fn; // where we will attach our new methods | ||
804 | var oldMomentProto = $.extend({}, newMomentProto); // copy of original moment methods | ||
805 | var allowValueOptimization; | ||
806 | var setUTCValues; // function defined below | ||
807 | var setLocalValues; // function defined below | ||
808 | |||
809 | |||
810 | // Creating | ||
811 | // ------------------------------------------------------------------------------------------------- | ||
812 | |||
813 | // Creates a new moment, similar to the vanilla moment(...) constructor, but with | ||
814 | // extra features (ambiguous time, enhanced formatting). When given an existing moment, | ||
815 | // it will function as a clone (and retain the zone of the moment). Anything else will | ||
816 | // result in a moment in the local zone. | ||
817 | fc.moment = function() { | ||
818 | return makeMoment(arguments); | ||
819 | }; | ||
820 | |||
821 | // Sames as fc.moment, but forces the resulting moment to be in the UTC timezone. | ||
822 | fc.moment.utc = function() { | ||
823 | var mom = makeMoment(arguments, true); | ||
824 | |||
825 | // Force it into UTC because makeMoment doesn't guarantee it | ||
826 | // (if given a pre-existing moment for example) | ||
827 | if (mom.hasTime()) { // don't give ambiguously-timed moments a UTC zone | ||
828 | mom.utc(); | ||
829 | } | ||
830 | |||
831 | return mom; | ||
832 | }; | ||
833 | |||
834 | // Same as fc.moment, but when given an ISO8601 string, the timezone offset is preserved. | ||
835 | // ISO8601 strings with no timezone offset will become ambiguously zoned. | ||
836 | fc.moment.parseZone = function() { | ||
837 | return makeMoment(arguments, true, true); | ||
838 | }; | ||
839 | |||
840 | // Builds an enhanced moment from args. When given an existing moment, it clones. When given a | ||
841 | // native Date, or called with no arguments (the current time), the resulting moment will be local. | ||
842 | // Anything else needs to be "parsed" (a string or an array), and will be affected by: | ||
843 | // parseAsUTC - if there is no zone information, should we parse the input in UTC? | ||
844 | // parseZone - if there is zone information, should we force the zone of the moment? | ||
845 | function makeMoment(args, parseAsUTC, parseZone) { | ||
846 | var input = args[0]; | ||
847 | var isSingleString = args.length == 1 && typeof input === 'string'; | ||
848 | var isAmbigTime; | ||
849 | var isAmbigZone; | ||
850 | var ambigMatch; | ||
851 | var mom; | ||
852 | |||
853 | if (moment.isMoment(input)) { | ||
854 | mom = moment.apply(null, args); // clone it | ||
855 | transferAmbigs(input, mom); // the ambig flags weren't transfered with the clone | ||
856 | } | ||
857 | else if (isNativeDate(input) || input === undefined) { | ||
858 | mom = moment.apply(null, args); // will be local | ||
859 | } | ||
860 | else { // "parsing" is required | ||
861 | isAmbigTime = false; | ||
862 | isAmbigZone = false; | ||
863 | |||
864 | if (isSingleString) { | ||
865 | if (ambigDateOfMonthRegex.test(input)) { | ||
866 | // accept strings like '2014-05', but convert to the first of the month | ||
867 | input += '-01'; | ||
868 | args = [ input ]; // for when we pass it on to moment's constructor | ||
869 | isAmbigTime = true; | ||
870 | isAmbigZone = true; | ||
871 | } | ||
872 | else if ((ambigMatch = ambigTimeOrZoneRegex.exec(input))) { | ||
873 | isAmbigTime = !ambigMatch[5]; // no time part? | ||
874 | isAmbigZone = true; | ||
875 | } | ||
876 | } | ||
877 | else if ($.isArray(input)) { | ||
878 | // arrays have no timezone information, so assume ambiguous zone | ||
879 | isAmbigZone = true; | ||
880 | } | ||
881 | // otherwise, probably a string with a format | ||
882 | |||
883 | if (parseAsUTC || isAmbigTime) { | ||
884 | mom = moment.utc.apply(moment, args); | ||
885 | } | ||
886 | else { | ||
887 | mom = moment.apply(null, args); | ||
888 | } | ||
889 | |||
890 | if (isAmbigTime) { | ||
891 | mom._ambigTime = true; | ||
892 | mom._ambigZone = true; // ambiguous time always means ambiguous zone | ||
893 | } | ||
894 | else if (parseZone) { // let's record the inputted zone somehow | ||
895 | if (isAmbigZone) { | ||
896 | mom._ambigZone = true; | ||
897 | } | ||
898 | else if (isSingleString) { | ||
899 | if (mom.utcOffset) { | ||
900 | mom.utcOffset(input); // if not a valid zone, will assign UTC | ||
901 | } | ||
902 | else { | ||
903 | mom.zone(input); // for moment-pre-2.9 | ||
904 | } | ||
905 | } | ||
906 | } | ||
907 | } | ||
908 | |||
909 | mom._fullCalendar = true; // flag for extended functionality | ||
910 | |||
911 | return mom; | ||
912 | } | ||
913 | |||
914 | |||
915 | // A clone method that works with the flags related to our enhanced functionality. | ||
916 | // In the future, use moment.momentProperties | ||
917 | newMomentProto.clone = function() { | ||
918 | var mom = oldMomentProto.clone.apply(this, arguments); | ||
919 | |||
920 | // these flags weren't transfered with the clone | ||
921 | transferAmbigs(this, mom); | ||
922 | if (this._fullCalendar) { | ||
923 | mom._fullCalendar = true; | ||
924 | } | ||
925 | |||
926 | return mom; | ||
927 | }; | ||
928 | |||
929 | |||
930 | // Week Number | ||
931 | // ------------------------------------------------------------------------------------------------- | ||
932 | |||
933 | |||
934 | // Returns the week number, considering the locale's custom week number calcuation | ||
935 | // `weeks` is an alias for `week` | ||
936 | newMomentProto.week = newMomentProto.weeks = function(input) { | ||
937 | var weekCalc = (this._locale || this._lang) // works pre-moment-2.8 | ||
938 | ._fullCalendar_weekCalc; | ||
939 | |||
940 | if (input == null && typeof weekCalc === 'function') { // custom function only works for getter | ||
941 | return weekCalc(this); | ||
942 | } | ||
943 | else if (weekCalc === 'ISO') { | ||
944 | return oldMomentProto.isoWeek.apply(this, arguments); // ISO getter/setter | ||
945 | } | ||
946 | |||
947 | return oldMomentProto.week.apply(this, arguments); // local getter/setter | ||
948 | }; | ||
949 | |||
950 | |||
951 | // Time-of-day | ||
952 | // ------------------------------------------------------------------------------------------------- | ||
953 | |||
954 | // GETTER | ||
955 | // Returns a Duration with the hours/minutes/seconds/ms values of the moment. | ||
956 | // If the moment has an ambiguous time, a duration of 00:00 will be returned. | ||
957 | // | ||
958 | // SETTER | ||
959 | // You can supply a Duration, a Moment, or a Duration-like argument. | ||
960 | // When setting the time, and the moment has an ambiguous time, it then becomes unambiguous. | ||
961 | newMomentProto.time = function(time) { | ||
962 | |||
963 | // Fallback to the original method (if there is one) if this moment wasn't created via FullCalendar. | ||
964 | // `time` is a generic enough method name where this precaution is necessary to avoid collisions w/ other plugins. | ||
965 | if (!this._fullCalendar) { | ||
966 | return oldMomentProto.time.apply(this, arguments); | ||
967 | } | ||
968 | |||
969 | if (time == null) { // getter | ||
970 | return moment.duration({ | ||
971 | hours: this.hours(), | ||
972 | minutes: this.minutes(), | ||
973 | seconds: this.seconds(), | ||
974 | milliseconds: this.milliseconds() | ||
975 | }); | ||
976 | } | ||
977 | else { // setter | ||
978 | |||
979 | this._ambigTime = false; // mark that the moment now has a time | ||
980 | |||
981 | if (!moment.isDuration(time) && !moment.isMoment(time)) { | ||
982 | time = moment.duration(time); | ||
983 | } | ||
984 | |||
985 | // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day). | ||
986 | // Only for Duration times, not Moment times. | ||
987 | var dayHours = 0; | ||
988 | if (moment.isDuration(time)) { | ||
989 | dayHours = Math.floor(time.asDays()) * 24; | ||
990 | } | ||
991 | |||
992 | // We need to set the individual fields. | ||
993 | // Can't use startOf('day') then add duration. In case of DST at start of day. | ||
994 | return this.hours(dayHours + time.hours()) | ||
995 | .minutes(time.minutes()) | ||
996 | .seconds(time.seconds()) | ||
997 | .milliseconds(time.milliseconds()); | ||
998 | } | ||
999 | }; | ||
1000 | |||
1001 | // Converts the moment to UTC, stripping out its time-of-day and timezone offset, | ||
1002 | // but preserving its YMD. A moment with a stripped time will display no time | ||
1003 | // nor timezone offset when .format() is called. | ||
1004 | newMomentProto.stripTime = function() { | ||
1005 | var a; | ||
1006 | |||
1007 | if (!this._ambigTime) { | ||
1008 | |||
1009 | // get the values before any conversion happens | ||
1010 | a = this.toArray(); // array of y/m/d/h/m/s/ms | ||
1011 | |||
1012 | // TODO: use keepLocalTime in the future | ||
1013 | this.utc(); // set the internal UTC flag (will clear the ambig flags) | ||
1014 | setUTCValues(this, a.slice(0, 3)); // set the year/month/date. time will be zero | ||
1015 | |||
1016 | // Mark the time as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(), | ||
1017 | // which clears all ambig flags. Same with setUTCValues with moment-timezone. | ||
1018 | this._ambigTime = true; | ||
1019 | this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset | ||
1020 | } | ||
1021 | |||
1022 | return this; // for chaining | ||
1023 | }; | ||
1024 | |||
1025 | // Returns if the moment has a non-ambiguous time (boolean) | ||
1026 | newMomentProto.hasTime = function() { | ||
1027 | return !this._ambigTime; | ||
1028 | }; | ||
1029 | |||
1030 | |||
1031 | // Timezone | ||
1032 | // ------------------------------------------------------------------------------------------------- | ||
1033 | |||
1034 | // Converts the moment to UTC, stripping out its timezone offset, but preserving its | ||
1035 | // YMD and time-of-day. A moment with a stripped timezone offset will display no | ||
1036 | // timezone offset when .format() is called. | ||
1037 | // TODO: look into Moment's keepLocalTime functionality | ||
1038 | newMomentProto.stripZone = function() { | ||
1039 | var a, wasAmbigTime; | ||
1040 | |||
1041 | if (!this._ambigZone) { | ||
1042 | |||
1043 | // get the values before any conversion happens | ||
1044 | a = this.toArray(); // array of y/m/d/h/m/s/ms | ||
1045 | wasAmbigTime = this._ambigTime; | ||
1046 | |||
1047 | this.utc(); // set the internal UTC flag (might clear the ambig flags, depending on Moment internals) | ||
1048 | setUTCValues(this, a); // will set the year/month/date/hours/minutes/seconds/ms | ||
1049 | |||
1050 | // the above call to .utc()/.utcOffset() unfortunately might clear the ambig flags, so restore | ||
1051 | this._ambigTime = wasAmbigTime || false; | ||
1052 | |||
1053 | // Mark the zone as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(), | ||
1054 | // which clears the ambig flags. Same with setUTCValues with moment-timezone. | ||
1055 | this._ambigZone = true; | ||
1056 | } | ||
1057 | |||
1058 | return this; // for chaining | ||
1059 | }; | ||
1060 | |||
1061 | // Returns of the moment has a non-ambiguous timezone offset (boolean) | ||
1062 | newMomentProto.hasZone = function() { | ||
1063 | return !this._ambigZone; | ||
1064 | }; | ||
1065 | |||
1066 | |||
1067 | // this method implicitly marks a zone | ||
1068 | newMomentProto.local = function() { | ||
1069 | var a = this.toArray(); // year,month,date,hours,minutes,seconds,ms as an array | ||
1070 | var wasAmbigZone = this._ambigZone; | ||
1071 | |||
1072 | oldMomentProto.local.apply(this, arguments); | ||
1073 | |||
1074 | // ensure non-ambiguous | ||
1075 | // this probably already happened via local() -> utcOffset(), but don't rely on Moment's internals | ||
1076 | this._ambigTime = false; | ||
1077 | this._ambigZone = false; | ||
1078 | |||
1079 | if (wasAmbigZone) { | ||
1080 | // If the moment was ambiguously zoned, the date fields were stored as UTC. | ||
1081 | // We want to preserve these, but in local time. | ||
1082 | // TODO: look into Moment's keepLocalTime functionality | ||
1083 | setLocalValues(this, a); | ||
1084 | } | ||
1085 | |||
1086 | return this; // for chaining | ||
1087 | }; | ||
1088 | |||
1089 | |||
1090 | // implicitly marks a zone | ||
1091 | newMomentProto.utc = function() { | ||
1092 | oldMomentProto.utc.apply(this, arguments); | ||
1093 | |||
1094 | // ensure non-ambiguous | ||
1095 | // this probably already happened via utc() -> utcOffset(), but don't rely on Moment's internals | ||
1096 | this._ambigTime = false; | ||
1097 | this._ambigZone = false; | ||
1098 | |||
1099 | return this; | ||
1100 | }; | ||
1101 | |||
1102 | |||
1103 | // methods for arbitrarily manipulating timezone offset. | ||
1104 | // should clear time/zone ambiguity when called. | ||
1105 | $.each([ | ||
1106 | 'zone', // only in moment-pre-2.9. deprecated afterwards | ||
1107 | 'utcOffset' | ||
1108 | ], function(i, name) { | ||
1109 | if (oldMomentProto[name]) { // original method exists? | ||
1110 | |||
1111 | // this method implicitly marks a zone (will probably get called upon .utc() and .local()) | ||
1112 | newMomentProto[name] = function(tzo) { | ||
1113 | |||
1114 | if (tzo != null) { // setter | ||
1115 | // these assignments needs to happen before the original zone method is called. | ||
1116 | // I forget why, something to do with a browser crash. | ||
1117 | this._ambigTime = false; | ||
1118 | this._ambigZone = false; | ||
1119 | } | ||
1120 | |||
1121 | return oldMomentProto[name].apply(this, arguments); | ||
1122 | }; | ||
1123 | } | ||
1124 | }); | ||
1125 | |||
1126 | |||
1127 | // Formatting | ||
1128 | // ------------------------------------------------------------------------------------------------- | ||
1129 | |||
1130 | newMomentProto.format = function() { | ||
1131 | if (this._fullCalendar && arguments[0]) { // an enhanced moment? and a format string provided? | ||
1132 | return formatDate(this, arguments[0]); // our extended formatting | ||
1133 | } | ||
1134 | if (this._ambigTime) { | ||
1135 | return oldMomentFormat(this, 'YYYY-MM-DD'); | ||
1136 | } | ||
1137 | if (this._ambigZone) { | ||
1138 | return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss'); | ||
1139 | } | ||
1140 | return oldMomentProto.format.apply(this, arguments); | ||
1141 | }; | ||
1142 | |||
1143 | newMomentProto.toISOString = function() { | ||
1144 | if (this._ambigTime) { | ||
1145 | return oldMomentFormat(this, 'YYYY-MM-DD'); | ||
1146 | } | ||
1147 | if (this._ambigZone) { | ||
1148 | return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss'); | ||
1149 | } | ||
1150 | return oldMomentProto.toISOString.apply(this, arguments); | ||
1151 | }; | ||
1152 | |||
1153 | |||
1154 | // Querying | ||
1155 | // ------------------------------------------------------------------------------------------------- | ||
1156 | |||
1157 | // Is the moment within the specified range? `end` is exclusive. | ||
1158 | // FYI, this method is not a standard Moment method, so always do our enhanced logic. | ||
1159 | newMomentProto.isWithin = function(start, end) { | ||
1160 | var a = commonlyAmbiguate([ this, start, end ]); | ||
1161 | return a[0] >= a[1] && a[0] < a[2]; | ||
1162 | }; | ||
1163 | |||
1164 | // When isSame is called with units, timezone ambiguity is normalized before the comparison happens. | ||
1165 | // If no units specified, the two moments must be identically the same, with matching ambig flags. | ||
1166 | newMomentProto.isSame = function(input, units) { | ||
1167 | var a; | ||
1168 | |||
1169 | // only do custom logic if this is an enhanced moment | ||
1170 | if (!this._fullCalendar) { | ||
1171 | return oldMomentProto.isSame.apply(this, arguments); | ||
1172 | } | ||
1173 | |||
1174 | if (units) { | ||
1175 | a = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times | ||
1176 | return oldMomentProto.isSame.call(a[0], a[1], units); | ||
1177 | } | ||
1178 | else { | ||
1179 | input = fc.moment.parseZone(input); // normalize input | ||
1180 | return oldMomentProto.isSame.call(this, input) && | ||
1181 | Boolean(this._ambigTime) === Boolean(input._ambigTime) && | ||
1182 | Boolean(this._ambigZone) === Boolean(input._ambigZone); | ||
1183 | } | ||
1184 | }; | ||
1185 | |||
1186 | // Make these query methods work with ambiguous moments | ||
1187 | $.each([ | ||
1188 | 'isBefore', | ||
1189 | 'isAfter' | ||
1190 | ], function(i, methodName) { | ||
1191 | newMomentProto[methodName] = function(input, units) { | ||
1192 | var a; | ||
1193 | |||
1194 | // only do custom logic if this is an enhanced moment | ||
1195 | if (!this._fullCalendar) { | ||
1196 | return oldMomentProto[methodName].apply(this, arguments); | ||
1197 | } | ||
1198 | |||
1199 | a = commonlyAmbiguate([ this, input ]); | ||
1200 | return oldMomentProto[methodName].call(a[0], a[1], units); | ||
1201 | }; | ||
1202 | }); | ||
1203 | |||
1204 | |||
1205 | // Misc Internals | ||
1206 | // ------------------------------------------------------------------------------------------------- | ||
1207 | |||
1208 | // given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated. | ||
1209 | // for example, of one moment has ambig time, but not others, all moments will have their time stripped. | ||
1210 | // set `preserveTime` to `true` to keep times, but only normalize zone ambiguity. | ||
1211 | // returns the original moments if no modifications are necessary. | ||
1212 | function commonlyAmbiguate(inputs, preserveTime) { | ||
1213 | var anyAmbigTime = false; | ||
1214 | var anyAmbigZone = false; | ||
1215 | var len = inputs.length; | ||
1216 | var moms = []; | ||
1217 | var i, mom; | ||
1218 | |||
1219 | // parse inputs into real moments and query their ambig flags | ||
1220 | for (i = 0; i < len; i++) { | ||
1221 | mom = inputs[i]; | ||
1222 | if (!moment.isMoment(mom)) { | ||
1223 | mom = fc.moment.parseZone(mom); | ||
1224 | } | ||
1225 | anyAmbigTime = anyAmbigTime || mom._ambigTime; | ||
1226 | anyAmbigZone = anyAmbigZone || mom._ambigZone; | ||
1227 | moms.push(mom); | ||
1228 | } | ||
1229 | |||
1230 | // strip each moment down to lowest common ambiguity | ||
1231 | // use clones to avoid modifying the original moments | ||
1232 | for (i = 0; i < len; i++) { | ||
1233 | mom = moms[i]; | ||
1234 | if (!preserveTime && anyAmbigTime && !mom._ambigTime) { | ||
1235 | moms[i] = mom.clone().stripTime(); | ||
1236 | } | ||
1237 | else if (anyAmbigZone && !mom._ambigZone) { | ||
1238 | moms[i] = mom.clone().stripZone(); | ||
1239 | } | ||
1240 | } | ||
1241 | |||
1242 | return moms; | ||
1243 | } | ||
1244 | |||
1245 | // Transfers all the flags related to ambiguous time/zone from the `src` moment to the `dest` moment | ||
1246 | // TODO: look into moment.momentProperties for this. | ||
1247 | function transferAmbigs(src, dest) { | ||
1248 | if (src._ambigTime) { | ||
1249 | dest._ambigTime = true; | ||
1250 | } | ||
1251 | else if (dest._ambigTime) { | ||
1252 | dest._ambigTime = false; | ||
1253 | } | ||
1254 | |||
1255 | if (src._ambigZone) { | ||
1256 | dest._ambigZone = true; | ||
1257 | } | ||
1258 | else if (dest._ambigZone) { | ||
1259 | dest._ambigZone = false; | ||
1260 | } | ||
1261 | } | ||
1262 | |||
1263 | |||
1264 | // Sets the year/month/date/etc values of the moment from the given array. | ||
1265 | // Inefficient because it calls each individual setter. | ||
1266 | function setMomentValues(mom, a) { | ||
1267 | mom.year(a[0] || 0) | ||
1268 | .month(a[1] || 0) | ||
1269 | .date(a[2] || 0) | ||
1270 | .hours(a[3] || 0) | ||
1271 | .minutes(a[4] || 0) | ||
1272 | .seconds(a[5] || 0) | ||
1273 | .milliseconds(a[6] || 0); | ||
1274 | } | ||
1275 | |||
1276 | // Can we set the moment's internal date directly? | ||
1277 | allowValueOptimization = '_d' in moment() && 'updateOffset' in moment; | ||
1278 | |||
1279 | // Utility function. Accepts a moment and an array of the UTC year/month/date/etc values to set. | ||
1280 | // Assumes the given moment is already in UTC mode. | ||
1281 | setUTCValues = allowValueOptimization ? function(mom, a) { | ||
1282 | // simlate what moment's accessors do | ||
1283 | mom._d.setTime(Date.UTC.apply(Date, a)); | ||
1284 | moment.updateOffset(mom, false); // keepTime=false | ||
1285 | } : setMomentValues; | ||
1286 | |||
1287 | // Utility function. Accepts a moment and an array of the local year/month/date/etc values to set. | ||
1288 | // Assumes the given moment is already in local mode. | ||
1289 | setLocalValues = allowValueOptimization ? function(mom, a) { | ||
1290 | // simlate what moment's accessors do | ||
1291 | mom._d.setTime(+new Date( // FYI, there is now way to apply an array of args to a constructor | ||
1292 | a[0] || 0, | ||
1293 | a[1] || 0, | ||
1294 | a[2] || 0, | ||
1295 | a[3] || 0, | ||
1296 | a[4] || 0, | ||
1297 | a[5] || 0, | ||
1298 | a[6] || 0 | ||
1299 | )); | ||
1300 | moment.updateOffset(mom, false); // keepTime=false | ||
1301 | } : setMomentValues; | ||
1302 | |||
1303 | ;; | ||
1304 | |||
1305 | // Single Date Formatting | ||
1306 | // ------------------------------------------------------------------------------------------------- | ||
1307 | |||
1308 | |||
1309 | // call this if you want Moment's original format method to be used | ||
1310 | function oldMomentFormat(mom, formatStr) { | ||
1311 | return oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js | ||
1312 | } | ||
1313 | |||
1314 | |||
1315 | // Formats `date` with a Moment formatting string, but allow our non-zero areas and | ||
1316 | // additional token. | ||
1317 | function formatDate(date, formatStr) { | ||
1318 | return formatDateWithChunks(date, getFormatStringChunks(formatStr)); | ||
1319 | } | ||
1320 | |||
1321 | |||
1322 | function formatDateWithChunks(date, chunks) { | ||
1323 | var s = ''; | ||
1324 | var i; | ||
1325 | |||
1326 | for (i=0; i<chunks.length; i++) { | ||
1327 | s += formatDateWithChunk(date, chunks[i]); | ||
1328 | } | ||
1329 | |||
1330 | return s; | ||
1331 | } | ||
1332 | |||
1333 | |||
1334 | // addition formatting tokens we want recognized | ||
1335 | var tokenOverrides = { | ||
1336 | t: function(date) { // "a" or "p" | ||
1337 | return oldMomentFormat(date, 'a').charAt(0); | ||
1338 | }, | ||
1339 | T: function(date) { // "A" or "P" | ||
1340 | return oldMomentFormat(date, 'A').charAt(0); | ||
1341 | } | ||
1342 | }; | ||
1343 | |||
1344 | |||
1345 | function formatDateWithChunk(date, chunk) { | ||
1346 | var token; | ||
1347 | var maybeStr; | ||
1348 | |||
1349 | if (typeof chunk === 'string') { // a literal string | ||
1350 | return chunk; | ||
1351 | } | ||
1352 | else if ((token = chunk.token)) { // a token, like "YYYY" | ||
1353 | if (tokenOverrides[token]) { | ||
1354 | return tokenOverrides[token](date); // use our custom token | ||
1355 | } | ||
1356 | return oldMomentFormat(date, token); | ||
1357 | } | ||
1358 | else if (chunk.maybe) { // a grouping of other chunks that must be non-zero | ||
1359 | maybeStr = formatDateWithChunks(date, chunk.maybe); | ||
1360 | if (maybeStr.match(/[1-9]/)) { | ||
1361 | return maybeStr; | ||
1362 | } | ||
1363 | } | ||
1364 | |||
1365 | return ''; | ||
1366 | } | ||
1367 | |||
1368 | |||
1369 | // Date Range Formatting | ||
1370 | // ------------------------------------------------------------------------------------------------- | ||
1371 | // TODO: make it work with timezone offset | ||
1372 | |||
1373 | // Using a formatting string meant for a single date, generate a range string, like | ||
1374 | // "Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ. | ||
1375 | // If the dates are the same as far as the format string is concerned, just return a single | ||
1376 | // rendering of one date, without any separator. | ||
1377 | function formatRange(date1, date2, formatStr, separator, isRTL) { | ||
1378 | var localeData; | ||
1379 | |||
1380 | date1 = fc.moment.parseZone(date1); | ||
1381 | date2 = fc.moment.parseZone(date2); | ||
1382 | |||
1383 | localeData = (date1.localeData || date1.lang).call(date1); // works with moment-pre-2.8 | ||
1384 | |||
1385 | // Expand localized format strings, like "LL" -> "MMMM D YYYY" | ||
1386 | formatStr = localeData.longDateFormat(formatStr) || formatStr; | ||
1387 | // BTW, this is not important for `formatDate` because it is impossible to put custom tokens | ||
1388 | // or non-zero areas in Moment's localized format strings. | ||
1389 | |||
1390 | separator = separator || ' - '; | ||
1391 | |||
1392 | return formatRangeWithChunks( | ||
1393 | date1, | ||
1394 | date2, | ||
1395 | getFormatStringChunks(formatStr), | ||
1396 | separator, | ||
1397 | isRTL | ||
1398 | ); | ||
1399 | } | ||
1400 | fc.formatRange = formatRange; // expose | ||
1401 | |||
1402 | |||
1403 | function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) { | ||
1404 | var chunkStr; // the rendering of the chunk | ||
1405 | var leftI; | ||
1406 | var leftStr = ''; | ||
1407 | var rightI; | ||
1408 | var rightStr = ''; | ||
1409 | var middleI; | ||
1410 | var middleStr1 = ''; | ||
1411 | var middleStr2 = ''; | ||
1412 | var middleStr = ''; | ||
1413 | |||
1414 | // Start at the leftmost side of the formatting string and continue until you hit a token | ||
1415 | // that is not the same between dates. | ||
1416 | for (leftI=0; leftI<chunks.length; leftI++) { | ||
1417 | chunkStr = formatSimilarChunk(date1, date2, chunks[leftI]); | ||
1418 | if (chunkStr === false) { | ||
1419 | break; | ||
1420 | } | ||
1421 | leftStr += chunkStr; | ||
1422 | } | ||
1423 | |||
1424 | // Similarly, start at the rightmost side of the formatting string and move left | ||
1425 | for (rightI=chunks.length-1; rightI>leftI; rightI--) { | ||
1426 | chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]); | ||
1427 | if (chunkStr === false) { | ||
1428 | break; | ||
1429 | } | ||
1430 | rightStr = chunkStr + rightStr; | ||
1431 | } | ||
1432 | |||
1433 | // The area in the middle is different for both of the dates. | ||
1434 | // Collect them distinctly so we can jam them together later. | ||
1435 | for (middleI=leftI; middleI<=rightI; middleI++) { | ||
1436 | middleStr1 += formatDateWithChunk(date1, chunks[middleI]); | ||
1437 | middleStr2 += formatDateWithChunk(date2, chunks[middleI]); | ||
1438 | } | ||
1439 | |||
1440 | if (middleStr1 || middleStr2) { | ||
1441 | if (isRTL) { | ||
1442 | middleStr = middleStr2 + separator + middleStr1; | ||
1443 | } | ||
1444 | else { | ||
1445 | middleStr = middleStr1 + separator + middleStr2; | ||
1446 | } | ||
1447 | } | ||
1448 | |||
1449 | return leftStr + middleStr + rightStr; | ||
1450 | } | ||
1451 | |||
1452 | |||
1453 | var similarUnitMap = { | ||
1454 | Y: 'year', | ||
1455 | M: 'month', | ||
1456 | D: 'day', // day of month | ||
1457 | d: 'day', // day of week | ||
1458 | // prevents a separator between anything time-related... | ||
1459 | A: 'second', // AM/PM | ||
1460 | a: 'second', // am/pm | ||
1461 | T: 'second', // A/P | ||
1462 | t: 'second', // a/p | ||
1463 | H: 'second', // hour (24) | ||
1464 | h: 'second', // hour (12) | ||
1465 | m: 'second', // minute | ||
1466 | s: 'second' // second | ||
1467 | }; | ||
1468 | // TODO: week maybe? | ||
1469 | |||
1470 | |||
1471 | // Given a formatting chunk, and given that both dates are similar in the regard the | ||
1472 | // formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`. | ||
1473 | function formatSimilarChunk(date1, date2, chunk) { | ||
1474 | var token; | ||
1475 | var unit; | ||
1476 | |||
1477 | if (typeof chunk === 'string') { // a literal string | ||
1478 | return chunk; | ||
1479 | } | ||
1480 | else if ((token = chunk.token)) { | ||
1481 | unit = similarUnitMap[token.charAt(0)]; | ||
1482 | // are the dates the same for this unit of measurement? | ||
1483 | if (unit && date1.isSame(date2, unit)) { | ||
1484 | return oldMomentFormat(date1, token); // would be the same if we used `date2` | ||
1485 | // BTW, don't support custom tokens | ||
1486 | } | ||
1487 | } | ||
1488 | |||
1489 | return false; // the chunk is NOT the same for the two dates | ||
1490 | // BTW, don't support splitting on non-zero areas | ||
1491 | } | ||
1492 | |||
1493 | |||
1494 | // Chunking Utils | ||
1495 | // ------------------------------------------------------------------------------------------------- | ||
1496 | |||
1497 | |||
1498 | var formatStringChunkCache = {}; | ||
1499 | |||
1500 | |||
1501 | function getFormatStringChunks(formatStr) { | ||
1502 | if (formatStr in formatStringChunkCache) { | ||
1503 | return formatStringChunkCache[formatStr]; | ||
1504 | } | ||
1505 | return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr)); | ||
1506 | } | ||
1507 | |||
1508 | |||
1509 | // Break the formatting string into an array of chunks | ||
1510 | function chunkFormatString(formatStr) { | ||
1511 | var chunks = []; | ||
1512 | var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination | ||
1513 | var match; | ||
1514 | |||
1515 | while ((match = chunker.exec(formatStr))) { | ||
1516 | if (match[1]) { // a literal string inside [ ... ] | ||
1517 | chunks.push(match[1]); | ||
1518 | } | ||
1519 | else if (match[2]) { // non-zero formatting inside ( ... ) | ||
1520 | chunks.push({ maybe: chunkFormatString(match[2]) }); | ||
1521 | } | ||
1522 | else if (match[3]) { // a formatting token | ||
1523 | chunks.push({ token: match[3] }); | ||
1524 | } | ||
1525 | else if (match[5]) { // an unenclosed literal string | ||
1526 | chunks.push(match[5]); | ||
1527 | } | ||
1528 | } | ||
1529 | |||
1530 | return chunks; | ||
1531 | } | ||
1532 | |||
1533 | ;; | ||
1534 | |||
1535 | fc.Class = Class; // export | ||
1536 | |||
1537 | // class that all other classes will inherit from | ||
1538 | function Class() { } | ||
1539 | |||
1540 | // called upon a class to create a subclass | ||
1541 | Class.extend = function(members) { | ||
1542 | var superClass = this; | ||
1543 | var subClass; | ||
1544 | |||
1545 | members = members || {}; | ||
1546 | |||
1547 | // ensure a constructor for the subclass, forwarding all arguments to the super-constructor if it doesn't exist | ||
1548 | if (hasOwnProp(members, 'constructor')) { | ||
1549 | subClass = members.constructor; | ||
1550 | } | ||
1551 | if (typeof subClass !== 'function') { | ||
1552 | subClass = members.constructor = function() { | ||
1553 | superClass.apply(this, arguments); | ||
1554 | }; | ||
1555 | } | ||
1556 | |||
1557 | // build the base prototype for the subclass, which is an new object chained to the superclass's prototype | ||
1558 | subClass.prototype = createObject(superClass.prototype); | ||
1559 | |||
1560 | // copy each member variable/method onto the the subclass's prototype | ||
1561 | copyOwnProps(members, subClass.prototype); | ||
1562 | copyNativeMethods(members, subClass.prototype); // hack for IE8 | ||
1563 | |||
1564 | // copy over all class variables/methods to the subclass, such as `extend` and `mixin` | ||
1565 | copyOwnProps(superClass, subClass); | ||
1566 | |||
1567 | return subClass; | ||
1568 | }; | ||
1569 | |||
1570 | // adds new member variables/methods to the class's prototype. | ||
1571 | // can be called with another class, or a plain object hash containing new members. | ||
1572 | Class.mixin = function(members) { | ||
1573 | copyOwnProps(members.prototype || members, this.prototype); | ||
1574 | }; | ||
1575 | ;; | ||
1576 | |||
1577 | /* A rectangular panel that is absolutely positioned over other content | ||
1578 | ------------------------------------------------------------------------------------------------------------------------ | ||
1579 | Options: | ||
1580 | - className (string) | ||
1581 | - content (HTML string or jQuery element set) | ||
1582 | - parentEl | ||
1583 | - top | ||
1584 | - left | ||
1585 | - right (the x coord of where the right edge should be. not a "CSS" right) | ||
1586 | - autoHide (boolean) | ||
1587 | - show (callback) | ||
1588 | - hide (callback) | ||
1589 | */ | ||
1590 | |||
1591 | var Popover = Class.extend({ | ||
1592 | |||
1593 | isHidden: true, | ||
1594 | options: null, | ||
1595 | el: null, // the container element for the popover. generated by this object | ||
1596 | documentMousedownProxy: null, // document mousedown handler bound to `this` | ||
1597 | margin: 10, // the space required between the popover and the edges of the scroll container | ||
1598 | |||
1599 | |||
1600 | constructor: function(options) { | ||
1601 | this.options = options || {}; | ||
1602 | }, | ||
1603 | |||
1604 | |||
1605 | // Shows the popover on the specified position. Renders it if not already | ||
1606 | show: function() { | ||
1607 | if (this.isHidden) { | ||
1608 | if (!this.el) { | ||
1609 | this.render(); | ||
1610 | } | ||
1611 | this.el.show(); | ||
1612 | this.position(); | ||
1613 | this.isHidden = false; | ||
1614 | this.trigger('show'); | ||
1615 | } | ||
1616 | }, | ||
1617 | |||
1618 | |||
1619 | // Hides the popover, through CSS, but does not remove it from the DOM | ||
1620 | hide: function() { | ||
1621 | if (!this.isHidden) { | ||
1622 | this.el.hide(); | ||
1623 | this.isHidden = true; | ||
1624 | this.trigger('hide'); | ||
1625 | } | ||
1626 | }, | ||
1627 | |||
1628 | |||
1629 | // Creates `this.el` and renders content inside of it | ||
1630 | render: function() { | ||
1631 | var _this = this; | ||
1632 | var options = this.options; | ||
1633 | |||
1634 | this.el = $('<div class="fc-popover"/>') | ||
1635 | .addClass(options.className || '') | ||
1636 | .css({ | ||
1637 | // position initially to the top left to avoid creating scrollbars | ||
1638 | top: 0, | ||
1639 | left: 0 | ||
1640 | }) | ||
1641 | .append(options.content) | ||
1642 | .appendTo(options.parentEl); | ||
1643 | |||
1644 | // when a click happens on anything inside with a 'fc-close' className, hide the popover | ||
1645 | this.el.on('click', '.fc-close', function() { | ||
1646 | _this.hide(); | ||
1647 | }); | ||
1648 | |||
1649 | if (options.autoHide) { | ||
1650 | $(document).on('mousedown', this.documentMousedownProxy = proxy(this, 'documentMousedown')); | ||
1651 | } | ||
1652 | }, | ||
1653 | |||
1654 | |||
1655 | // Triggered when the user clicks *anywhere* in the document, for the autoHide feature | ||
1656 | documentMousedown: function(ev) { | ||
1657 | // only hide the popover if the click happened outside the popover | ||
1658 | if (this.el && !$(ev.target).closest(this.el).length) { | ||
1659 | this.hide(); | ||
1660 | } | ||
1661 | }, | ||
1662 | |||
1663 | |||
1664 | // Hides and unregisters any handlers | ||
1665 | destroy: function() { | ||
1666 | this.hide(); | ||
1667 | |||
1668 | if (this.el) { | ||
1669 | this.el.remove(); | ||
1670 | this.el = null; | ||
1671 | } | ||
1672 | |||
1673 | $(document).off('mousedown', this.documentMousedownProxy); | ||
1674 | }, | ||
1675 | |||
1676 | |||
1677 | // Positions the popover optimally, using the top/left/right options | ||
1678 | position: function() { | ||
1679 | var options = this.options; | ||
1680 | var origin = this.el.offsetParent().offset(); | ||
1681 | var width = this.el.outerWidth(); | ||
1682 | var height = this.el.outerHeight(); | ||
1683 | var windowEl = $(window); | ||
1684 | var viewportEl = getScrollParent(this.el); | ||
1685 | var viewportTop; | ||
1686 | var viewportLeft; | ||
1687 | var viewportOffset; | ||
1688 | var top; // the "position" (not "offset") values for the popover | ||
1689 | var left; // | ||
1690 | |||
1691 | // compute top and left | ||
1692 | top = options.top || 0; | ||
1693 | if (options.left !== undefined) { | ||
1694 | left = options.left; | ||
1695 | } | ||
1696 | else if (options.right !== undefined) { | ||
1697 | left = options.right - width; // derive the left value from the right value | ||
1698 | } | ||
1699 | else { | ||
1700 | left = 0; | ||
1701 | } | ||
1702 | |||
1703 | if (viewportEl.is(window) || viewportEl.is(document)) { // normalize getScrollParent's result | ||
1704 | viewportEl = windowEl; | ||
1705 | viewportTop = 0; // the window is always at the top left | ||
1706 | viewportLeft = 0; // (and .offset() won't work if called here) | ||
1707 | } | ||
1708 | else { | ||
1709 | viewportOffset = viewportEl.offset(); | ||
1710 | viewportTop = viewportOffset.top; | ||
1711 | viewportLeft = viewportOffset.left; | ||
1712 | } | ||
1713 | |||
1714 | // if the window is scrolled, it causes the visible area to be further down | ||
1715 | viewportTop += windowEl.scrollTop(); | ||
1716 | viewportLeft += windowEl.scrollLeft(); | ||
1717 | |||
1718 | // constrain to the view port. if constrained by two edges, give precedence to top/left | ||
1719 | if (options.viewportConstrain !== false) { | ||
1720 | top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin); | ||
1721 | top = Math.max(top, viewportTop + this.margin); | ||
1722 | left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin); | ||
1723 | left = Math.max(left, viewportLeft + this.margin); | ||
1724 | } | ||
1725 | |||
1726 | this.el.css({ | ||
1727 | top: top - origin.top, | ||
1728 | left: left - origin.left | ||
1729 | }); | ||
1730 | }, | ||
1731 | |||
1732 | |||
1733 | // Triggers a callback. Calls a function in the option hash of the same name. | ||
1734 | // Arguments beyond the first `name` are forwarded on. | ||
1735 | // TODO: better code reuse for this. Repeat code | ||
1736 | trigger: function(name) { | ||
1737 | if (this.options[name]) { | ||
1738 | this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); | ||
1739 | } | ||
1740 | } | ||
1741 | |||
1742 | }); | ||
1743 | |||
1744 | ;; | ||
1745 | |||
1746 | /* A "coordinate map" converts pixel coordinates into an associated cell, which has an associated date | ||
1747 | ------------------------------------------------------------------------------------------------------------------------ | ||
1748 | Common interface: | ||
1749 | |||
1750 | CoordMap.prototype = { | ||
1751 | build: function() {}, | ||
1752 | getCell: function(x, y) {} | ||
1753 | }; | ||
1754 | |||
1755 | */ | ||
1756 | |||
1757 | /* Coordinate map for a grid component | ||
1758 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
1759 | |||
1760 | var GridCoordMap = Class.extend({ | ||
1761 | |||
1762 | grid: null, // reference to the Grid | ||
1763 | rowCoords: null, // array of {top,bottom} objects | ||
1764 | colCoords: null, // array of {left,right} objects | ||
1765 | |||
1766 | containerEl: null, // container element that all coordinates are constrained to. optionally assigned | ||
1767 | bounds: null, | ||
1768 | |||
1769 | |||
1770 | constructor: function(grid) { | ||
1771 | this.grid = grid; | ||
1772 | }, | ||
1773 | |||
1774 | |||
1775 | // Queries the grid for the coordinates of all the cells | ||
1776 | build: function() { | ||
1777 | this.rowCoords = this.grid.computeRowCoords(); | ||
1778 | this.colCoords = this.grid.computeColCoords(); | ||
1779 | this.computeBounds(); | ||
1780 | }, | ||
1781 | |||
1782 | |||
1783 | // Clears the coordinates data to free up memory | ||
1784 | clear: function() { | ||
1785 | this.rowCoords = null; | ||
1786 | this.colCoords = null; | ||
1787 | }, | ||
1788 | |||
1789 | |||
1790 | // Given a coordinate of the document, gets the associated cell. If no cell is underneath, returns null | ||
1791 | getCell: function(x, y) { | ||
1792 | var rowCoords = this.rowCoords; | ||
1793 | var rowCnt = rowCoords.length; | ||
1794 | var colCoords = this.colCoords; | ||
1795 | var colCnt = colCoords.length; | ||
1796 | var hitRow = null; | ||
1797 | var hitCol = null; | ||
1798 | var i, coords; | ||
1799 | var cell; | ||
1800 | |||
1801 | if (this.inBounds(x, y)) { | ||
1802 | |||
1803 | for (i = 0; i < rowCnt; i++) { | ||
1804 | coords = rowCoords[i]; | ||
1805 | if (y >= coords.top && y < coords.bottom) { | ||
1806 | hitRow = i; | ||
1807 | break; | ||
1808 | } | ||
1809 | } | ||
1810 | |||
1811 | for (i = 0; i < colCnt; i++) { | ||
1812 | coords = colCoords[i]; | ||
1813 | if (x >= coords.left && x < coords.right) { | ||
1814 | hitCol = i; | ||
1815 | break; | ||
1816 | } | ||
1817 | } | ||
1818 | |||
1819 | if (hitRow !== null && hitCol !== null) { | ||
1820 | |||
1821 | cell = this.grid.getCell(hitRow, hitCol); // expected to return a fresh object we can modify | ||
1822 | cell.grid = this.grid; // for CellDragListener's isCellsEqual. dragging between grids | ||
1823 | |||
1824 | // make the coordinates available on the cell object | ||
1825 | $.extend(cell, rowCoords[hitRow], colCoords[hitCol]); | ||
1826 | |||
1827 | return cell; | ||
1828 | } | ||
1829 | } | ||
1830 | |||
1831 | return null; | ||
1832 | }, | ||
1833 | |||
1834 | |||
1835 | // If there is a containerEl, compute the bounds into min/max values | ||
1836 | computeBounds: function() { | ||
1837 | this.bounds = this.containerEl ? | ||
1838 | getClientRect(this.containerEl) : // area within scrollbars | ||
1839 | null; | ||
1840 | }, | ||
1841 | |||
1842 | |||
1843 | // Determines if the given coordinates are in bounds. If no `containerEl`, always true | ||
1844 | inBounds: function(x, y) { | ||
1845 | var bounds = this.bounds; | ||
1846 | |||
1847 | if (bounds) { | ||
1848 | return x >= bounds.left && x < bounds.right && y >= bounds.top && y < bounds.bottom; | ||
1849 | } | ||
1850 | |||
1851 | return true; | ||
1852 | } | ||
1853 | |||
1854 | }); | ||
1855 | |||
1856 | |||
1857 | /* Coordinate map that is a combination of multiple other coordinate maps | ||
1858 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
1859 | |||
1860 | var ComboCoordMap = Class.extend({ | ||
1861 | |||
1862 | coordMaps: null, // an array of CoordMaps | ||
1863 | |||
1864 | |||
1865 | constructor: function(coordMaps) { | ||
1866 | this.coordMaps = coordMaps; | ||
1867 | }, | ||
1868 | |||
1869 | |||
1870 | // Builds all coordMaps | ||
1871 | build: function() { | ||
1872 | var coordMaps = this.coordMaps; | ||
1873 | var i; | ||
1874 | |||
1875 | for (i = 0; i < coordMaps.length; i++) { | ||
1876 | coordMaps[i].build(); | ||
1877 | } | ||
1878 | }, | ||
1879 | |||
1880 | |||
1881 | // Queries all coordMaps for the cell underneath the given coordinates, returning the first result | ||
1882 | getCell: function(x, y) { | ||
1883 | var coordMaps = this.coordMaps; | ||
1884 | var cell = null; | ||
1885 | var i; | ||
1886 | |||
1887 | for (i = 0; i < coordMaps.length && !cell; i++) { | ||
1888 | cell = coordMaps[i].getCell(x, y); | ||
1889 | } | ||
1890 | |||
1891 | return cell; | ||
1892 | }, | ||
1893 | |||
1894 | |||
1895 | // Clears all coordMaps | ||
1896 | clear: function() { | ||
1897 | var coordMaps = this.coordMaps; | ||
1898 | var i; | ||
1899 | |||
1900 | for (i = 0; i < coordMaps.length; i++) { | ||
1901 | coordMaps[i].clear(); | ||
1902 | } | ||
1903 | } | ||
1904 | |||
1905 | }); | ||
1906 | |||
1907 | ;; | ||
1908 | |||
1909 | /* Tracks a drag's mouse movement, firing various handlers | ||
1910 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
1911 | |||
1912 | var DragListener = fc.DragListener = Class.extend({ | ||
1913 | |||
1914 | options: null, | ||
1915 | |||
1916 | isListening: false, | ||
1917 | isDragging: false, | ||
1918 | |||
1919 | // coordinates of the initial mousedown | ||
1920 | originX: null, | ||
1921 | originY: null, | ||
1922 | |||
1923 | // handler attached to the document, bound to the DragListener's `this` | ||
1924 | mousemoveProxy: null, | ||
1925 | mouseupProxy: null, | ||
1926 | |||
1927 | // for IE8 bug-fighting behavior, for now | ||
1928 | subjectEl: null, // the element being draged. optional | ||
1929 | subjectHref: null, | ||
1930 | |||
1931 | scrollEl: null, | ||
1932 | scrollBounds: null, // { top, bottom, left, right } | ||
1933 | scrollTopVel: null, // pixels per second | ||
1934 | scrollLeftVel: null, // pixels per second | ||
1935 | scrollIntervalId: null, // ID of setTimeout for scrolling animation loop | ||
1936 | scrollHandlerProxy: null, // this-scoped function for handling when scrollEl is scrolled | ||
1937 | |||
1938 | scrollSensitivity: 30, // pixels from edge for scrolling to start | ||
1939 | scrollSpeed: 200, // pixels per second, at maximum speed | ||
1940 | scrollIntervalMs: 50, // millisecond wait between scroll increment | ||
1941 | |||
1942 | |||
1943 | constructor: function(options) { | ||
1944 | options = options || {}; | ||
1945 | this.options = options; | ||
1946 | this.subjectEl = options.subjectEl; | ||
1947 | }, | ||
1948 | |||
1949 | |||
1950 | // Call this when the user does a mousedown. Will probably lead to startListening | ||
1951 | mousedown: function(ev) { | ||
1952 | if (isPrimaryMouseButton(ev)) { | ||
1953 | |||
1954 | ev.preventDefault(); // prevents native selection in most browsers | ||
1955 | |||
1956 | this.startListening(ev); | ||
1957 | |||
1958 | // start the drag immediately if there is no minimum distance for a drag start | ||
1959 | if (!this.options.distance) { | ||
1960 | this.startDrag(ev); | ||
1961 | } | ||
1962 | } | ||
1963 | }, | ||
1964 | |||
1965 | |||
1966 | // Call this to start tracking mouse movements | ||
1967 | startListening: function(ev) { | ||
1968 | var scrollParent; | ||
1969 | |||
1970 | if (!this.isListening) { | ||
1971 | |||
1972 | // grab scroll container and attach handler | ||
1973 | if (ev && this.options.scroll) { | ||
1974 | scrollParent = getScrollParent($(ev.target)); | ||
1975 | if (!scrollParent.is(window) && !scrollParent.is(document)) { | ||
1976 | this.scrollEl = scrollParent; | ||
1977 | |||
1978 | // scope to `this`, and use `debounce` to make sure rapid calls don't happen | ||
1979 | this.scrollHandlerProxy = debounce(proxy(this, 'scrollHandler'), 100); | ||
1980 | this.scrollEl.on('scroll', this.scrollHandlerProxy); | ||
1981 | } | ||
1982 | } | ||
1983 | |||
1984 | $(document) | ||
1985 | .on('mousemove', this.mousemoveProxy = proxy(this, 'mousemove')) | ||
1986 | .on('mouseup', this.mouseupProxy = proxy(this, 'mouseup')) | ||
1987 | .on('selectstart', this.preventDefault); // prevents native selection in IE<=8 | ||
1988 | |||
1989 | if (ev) { | ||
1990 | this.originX = ev.pageX; | ||
1991 | this.originY = ev.pageY; | ||
1992 | } | ||
1993 | else { | ||
1994 | // if no starting information was given, origin will be the topleft corner of the screen. | ||
1995 | // if so, dx/dy in the future will be the absolute coordinates. | ||
1996 | this.originX = 0; | ||
1997 | this.originY = 0; | ||
1998 | } | ||
1999 | |||
2000 | this.isListening = true; | ||
2001 | this.listenStart(ev); | ||
2002 | } | ||
2003 | }, | ||
2004 | |||
2005 | |||
2006 | // Called when drag listening has started (but a real drag has not necessarily began) | ||
2007 | listenStart: function(ev) { | ||
2008 | this.trigger('listenStart', ev); | ||
2009 | }, | ||
2010 | |||
2011 | |||
2012 | // Called when the user moves the mouse | ||
2013 | mousemove: function(ev) { | ||
2014 | var dx = ev.pageX - this.originX; | ||
2015 | var dy = ev.pageY - this.originY; | ||
2016 | var minDistance; | ||
2017 | var distanceSq; // current distance from the origin, squared | ||
2018 | |||
2019 | if (!this.isDragging) { // if not already dragging... | ||
2020 | // then start the drag if the minimum distance criteria is met | ||
2021 | minDistance = this.options.distance || 1; | ||
2022 | distanceSq = dx * dx + dy * dy; | ||
2023 | if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem | ||
2024 | this.startDrag(ev); | ||
2025 | } | ||
2026 | } | ||
2027 | |||
2028 | if (this.isDragging) { | ||
2029 | this.drag(dx, dy, ev); // report a drag, even if this mousemove initiated the drag | ||
2030 | } | ||
2031 | }, | ||
2032 | |||
2033 | |||
2034 | // Call this to initiate a legitimate drag. | ||
2035 | // This function is called internally from this class, but can also be called explicitly from outside | ||
2036 | startDrag: function(ev) { | ||
2037 | |||
2038 | if (!this.isListening) { // startDrag must have manually initiated | ||
2039 | this.startListening(); | ||
2040 | } | ||
2041 | |||
2042 | if (!this.isDragging) { | ||
2043 | this.isDragging = true; | ||
2044 | this.dragStart(ev); | ||
2045 | } | ||
2046 | }, | ||
2047 | |||
2048 | |||
2049 | // Called when the actual drag has started (went beyond minDistance) | ||
2050 | dragStart: function(ev) { | ||
2051 | var subjectEl = this.subjectEl; | ||
2052 | |||
2053 | this.trigger('dragStart', ev); | ||
2054 | |||
2055 | // remove a mousedown'd <a>'s href so it is not visited (IE8 bug) | ||
2056 | if ((this.subjectHref = subjectEl ? subjectEl.attr('href') : null)) { | ||
2057 | subjectEl.removeAttr('href'); | ||
2058 | } | ||
2059 | }, | ||
2060 | |||
2061 | |||
2062 | // Called while the mouse is being moved and when we know a legitimate drag is taking place | ||
2063 | drag: function(dx, dy, ev) { | ||
2064 | this.trigger('drag', dx, dy, ev); | ||
2065 | this.updateScroll(ev); // will possibly cause scrolling | ||
2066 | }, | ||
2067 | |||
2068 | |||
2069 | // Called when the user does a mouseup | ||
2070 | mouseup: function(ev) { | ||
2071 | this.stopListening(ev); | ||
2072 | }, | ||
2073 | |||
2074 | |||
2075 | // Called when the drag is over. Will not cause listening to stop however. | ||
2076 | // A concluding 'cellOut' event will NOT be triggered. | ||
2077 | stopDrag: function(ev) { | ||
2078 | if (this.isDragging) { | ||
2079 | this.stopScrolling(); | ||
2080 | this.dragStop(ev); | ||
2081 | this.isDragging = false; | ||
2082 | } | ||
2083 | }, | ||
2084 | |||
2085 | |||
2086 | // Called when dragging has been stopped | ||
2087 | dragStop: function(ev) { | ||
2088 | var _this = this; | ||
2089 | |||
2090 | this.trigger('dragStop', ev); | ||
2091 | |||
2092 | // restore a mousedown'd <a>'s href (for IE8 bug) | ||
2093 | setTimeout(function() { // must be outside of the click's execution | ||
2094 | if (_this.subjectHref) { | ||
2095 | _this.subjectEl.attr('href', _this.subjectHref); | ||
2096 | } | ||
2097 | }, 0); | ||
2098 | }, | ||
2099 | |||
2100 | |||
2101 | // Call this to stop listening to the user's mouse events | ||
2102 | stopListening: function(ev) { | ||
2103 | this.stopDrag(ev); // if there's a current drag, kill it | ||
2104 | |||
2105 | if (this.isListening) { | ||
2106 | |||
2107 | // remove the scroll handler if there is a scrollEl | ||
2108 | if (this.scrollEl) { | ||
2109 | this.scrollEl.off('scroll', this.scrollHandlerProxy); | ||
2110 | this.scrollHandlerProxy = null; | ||
2111 | } | ||
2112 | |||
2113 | $(document) | ||
2114 | .off('mousemove', this.mousemoveProxy) | ||
2115 | .off('mouseup', this.mouseupProxy) | ||
2116 | .off('selectstart', this.preventDefault); | ||
2117 | |||
2118 | this.mousemoveProxy = null; | ||
2119 | this.mouseupProxy = null; | ||
2120 | |||
2121 | this.isListening = false; | ||
2122 | this.listenStop(ev); | ||
2123 | } | ||
2124 | }, | ||
2125 | |||
2126 | |||
2127 | // Called when drag listening has stopped | ||
2128 | listenStop: function(ev) { | ||
2129 | this.trigger('listenStop', ev); | ||
2130 | }, | ||
2131 | |||
2132 | |||
2133 | // Triggers a callback. Calls a function in the option hash of the same name. | ||
2134 | // Arguments beyond the first `name` are forwarded on. | ||
2135 | trigger: function(name) { | ||
2136 | if (this.options[name]) { | ||
2137 | this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); | ||
2138 | } | ||
2139 | }, | ||
2140 | |||
2141 | |||
2142 | // Stops a given mouse event from doing it's native browser action. In our case, text selection. | ||
2143 | preventDefault: function(ev) { | ||
2144 | ev.preventDefault(); | ||
2145 | }, | ||
2146 | |||
2147 | |||
2148 | /* Scrolling | ||
2149 | ------------------------------------------------------------------------------------------------------------------*/ | ||
2150 | |||
2151 | |||
2152 | // Computes and stores the bounding rectangle of scrollEl | ||
2153 | computeScrollBounds: function() { | ||
2154 | var el = this.scrollEl; | ||
2155 | |||
2156 | this.scrollBounds = el ? getOuterRect(el) : null; | ||
2157 | // TODO: use getClientRect in future. but prevents auto scrolling when on top of scrollbars | ||
2158 | }, | ||
2159 | |||
2160 | |||
2161 | // Called when the dragging is in progress and scrolling should be updated | ||
2162 | updateScroll: function(ev) { | ||
2163 | var sensitivity = this.scrollSensitivity; | ||
2164 | var bounds = this.scrollBounds; | ||
2165 | var topCloseness, bottomCloseness; | ||
2166 | var leftCloseness, rightCloseness; | ||
2167 | var topVel = 0; | ||
2168 | var leftVel = 0; | ||
2169 | |||
2170 | if (bounds) { // only scroll if scrollEl exists | ||
2171 | |||
2172 | // compute closeness to edges. valid range is from 0.0 - 1.0 | ||
2173 | topCloseness = (sensitivity - (ev.pageY - bounds.top)) / sensitivity; | ||
2174 | bottomCloseness = (sensitivity - (bounds.bottom - ev.pageY)) / sensitivity; | ||
2175 | leftCloseness = (sensitivity - (ev.pageX - bounds.left)) / sensitivity; | ||
2176 | rightCloseness = (sensitivity - (bounds.right - ev.pageX)) / sensitivity; | ||
2177 | |||
2178 | // translate vertical closeness into velocity. | ||
2179 | // mouse must be completely in bounds for velocity to happen. | ||
2180 | if (topCloseness >= 0 && topCloseness <= 1) { | ||
2181 | topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up | ||
2182 | } | ||
2183 | else if (bottomCloseness >= 0 && bottomCloseness <= 1) { | ||
2184 | topVel = bottomCloseness * this.scrollSpeed; | ||
2185 | } | ||
2186 | |||
2187 | // translate horizontal closeness into velocity | ||
2188 | if (leftCloseness >= 0 && leftCloseness <= 1) { | ||
2189 | leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left | ||
2190 | } | ||
2191 | else if (rightCloseness >= 0 && rightCloseness <= 1) { | ||
2192 | leftVel = rightCloseness * this.scrollSpeed; | ||
2193 | } | ||
2194 | } | ||
2195 | |||
2196 | this.setScrollVel(topVel, leftVel); | ||
2197 | }, | ||
2198 | |||
2199 | |||
2200 | // Sets the speed-of-scrolling for the scrollEl | ||
2201 | setScrollVel: function(topVel, leftVel) { | ||
2202 | |||
2203 | this.scrollTopVel = topVel; | ||
2204 | this.scrollLeftVel = leftVel; | ||
2205 | |||
2206 | this.constrainScrollVel(); // massages into realistic values | ||
2207 | |||
2208 | // if there is non-zero velocity, and an animation loop hasn't already started, then START | ||
2209 | if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) { | ||
2210 | this.scrollIntervalId = setInterval( | ||
2211 | proxy(this, 'scrollIntervalFunc'), // scope to `this` | ||
2212 | this.scrollIntervalMs | ||
2213 | ); | ||
2214 | } | ||
2215 | }, | ||
2216 | |||
2217 | |||
2218 | // Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way | ||
2219 | constrainScrollVel: function() { | ||
2220 | var el = this.scrollEl; | ||
2221 | |||
2222 | if (this.scrollTopVel < 0) { // scrolling up? | ||
2223 | if (el.scrollTop() <= 0) { // already scrolled all the way up? | ||
2224 | this.scrollTopVel = 0; | ||
2225 | } | ||
2226 | } | ||
2227 | else if (this.scrollTopVel > 0) { // scrolling down? | ||
2228 | if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down? | ||
2229 | this.scrollTopVel = 0; | ||
2230 | } | ||
2231 | } | ||
2232 | |||
2233 | if (this.scrollLeftVel < 0) { // scrolling left? | ||
2234 | if (el.scrollLeft() <= 0) { // already scrolled all the left? | ||
2235 | this.scrollLeftVel = 0; | ||
2236 | } | ||
2237 | } | ||
2238 | else if (this.scrollLeftVel > 0) { // scrolling right? | ||
2239 | if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right? | ||
2240 | this.scrollLeftVel = 0; | ||
2241 | } | ||
2242 | } | ||
2243 | }, | ||
2244 | |||
2245 | |||
2246 | // This function gets called during every iteration of the scrolling animation loop | ||
2247 | scrollIntervalFunc: function() { | ||
2248 | var el = this.scrollEl; | ||
2249 | var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by | ||
2250 | |||
2251 | // change the value of scrollEl's scroll | ||
2252 | if (this.scrollTopVel) { | ||
2253 | el.scrollTop(el.scrollTop() + this.scrollTopVel * frac); | ||
2254 | } | ||
2255 | if (this.scrollLeftVel) { | ||
2256 | el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac); | ||
2257 | } | ||
2258 | |||
2259 | this.constrainScrollVel(); // since the scroll values changed, recompute the velocities | ||
2260 | |||
2261 | // if scrolled all the way, which causes the vels to be zero, stop the animation loop | ||
2262 | if (!this.scrollTopVel && !this.scrollLeftVel) { | ||
2263 | this.stopScrolling(); | ||
2264 | } | ||
2265 | }, | ||
2266 | |||
2267 | |||
2268 | // Kills any existing scrolling animation loop | ||
2269 | stopScrolling: function() { | ||
2270 | if (this.scrollIntervalId) { | ||
2271 | clearInterval(this.scrollIntervalId); | ||
2272 | this.scrollIntervalId = null; | ||
2273 | |||
2274 | // when all done with scrolling, recompute positions since they probably changed | ||
2275 | this.scrollStop(); | ||
2276 | } | ||
2277 | }, | ||
2278 | |||
2279 | |||
2280 | // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce) | ||
2281 | scrollHandler: function() { | ||
2282 | // recompute all coordinates, but *only* if this is *not* part of our scrolling animation | ||
2283 | if (!this.scrollIntervalId) { | ||
2284 | this.scrollStop(); | ||
2285 | } | ||
2286 | }, | ||
2287 | |||
2288 | |||
2289 | // Called when scrolling has stopped, whether through auto scroll, or the user scrolling | ||
2290 | scrollStop: function() { | ||
2291 | } | ||
2292 | |||
2293 | }); | ||
2294 | |||
2295 | ;; | ||
2296 | |||
2297 | /* Tracks mouse movements over a CoordMap and raises events about which cell the mouse is over. | ||
2298 | ------------------------------------------------------------------------------------------------------------------------ | ||
2299 | options: | ||
2300 | - subjectEl | ||
2301 | - subjectCenter | ||
2302 | */ | ||
2303 | |||
2304 | var CellDragListener = DragListener.extend({ | ||
2305 | |||
2306 | coordMap: null, // converts coordinates to date cells | ||
2307 | origCell: null, // the cell the mouse was over when listening started | ||
2308 | cell: null, // the cell the mouse is over | ||
2309 | coordAdjust: null, // delta that will be added to the mouse coordinates when computing collisions | ||
2310 | |||
2311 | |||
2312 | constructor: function(coordMap, options) { | ||
2313 | DragListener.prototype.constructor.call(this, options); // call the super-constructor | ||
2314 | |||
2315 | this.coordMap = coordMap; | ||
2316 | }, | ||
2317 | |||
2318 | |||
2319 | // Called when drag listening starts (but a real drag has not necessarily began). | ||
2320 | // ev might be undefined if dragging was started manually. | ||
2321 | listenStart: function(ev) { | ||
2322 | var subjectEl = this.subjectEl; | ||
2323 | var subjectRect; | ||
2324 | var origPoint; | ||
2325 | var point; | ||
2326 | |||
2327 | DragListener.prototype.listenStart.apply(this, arguments); // call the super-method | ||
2328 | |||
2329 | this.computeCoords(); | ||
2330 | |||
2331 | if (ev) { | ||
2332 | origPoint = { left: ev.pageX, top: ev.pageY }; | ||
2333 | point = origPoint; | ||
2334 | |||
2335 | // constrain the point to bounds of the element being dragged | ||
2336 | if (subjectEl) { | ||
2337 | subjectRect = getOuterRect(subjectEl); // used for centering as well | ||
2338 | point = constrainPoint(point, subjectRect); | ||
2339 | } | ||
2340 | |||
2341 | this.origCell = this.getCell(point.left, point.top); | ||
2342 | |||
2343 | // treat the center of the subject as the collision point? | ||
2344 | if (subjectEl && this.options.subjectCenter) { | ||
2345 | |||
2346 | // only consider the area the subject overlaps the cell. best for large subjects | ||
2347 | if (this.origCell) { | ||
2348 | subjectRect = intersectRects(this.origCell, subjectRect) || | ||
2349 | subjectRect; // in case there is no intersection | ||
2350 | } | ||
2351 | |||
2352 | point = getRectCenter(subjectRect); | ||
2353 | } | ||
2354 | |||
2355 | this.coordAdjust = diffPoints(point, origPoint); // point - origPoint | ||
2356 | } | ||
2357 | else { | ||
2358 | this.origCell = null; | ||
2359 | this.coordAdjust = null; | ||
2360 | } | ||
2361 | }, | ||
2362 | |||
2363 | |||
2364 | // Recomputes the drag-critical positions of elements | ||
2365 | computeCoords: function() { | ||
2366 | this.coordMap.build(); | ||
2367 | this.computeScrollBounds(); | ||
2368 | }, | ||
2369 | |||
2370 | |||
2371 | // Called when the actual drag has started | ||
2372 | dragStart: function(ev) { | ||
2373 | var cell; | ||
2374 | |||
2375 | DragListener.prototype.dragStart.apply(this, arguments); // call the super-method | ||
2376 | |||
2377 | cell = this.getCell(ev.pageX, ev.pageY); // might be different from this.origCell if the min-distance is large | ||
2378 | |||
2379 | // report the initial cell the mouse is over | ||
2380 | // especially important if no min-distance and drag starts immediately | ||
2381 | if (cell) { | ||
2382 | this.cellOver(cell); | ||
2383 | } | ||
2384 | }, | ||
2385 | |||
2386 | |||
2387 | // Called when the drag moves | ||
2388 | drag: function(dx, dy, ev) { | ||
2389 | var cell; | ||
2390 | |||
2391 | DragListener.prototype.drag.apply(this, arguments); // call the super-method | ||
2392 | |||
2393 | cell = this.getCell(ev.pageX, ev.pageY); | ||
2394 | |||
2395 | if (!isCellsEqual(cell, this.cell)) { // a different cell than before? | ||
2396 | if (this.cell) { | ||
2397 | this.cellOut(); | ||
2398 | } | ||
2399 | if (cell) { | ||
2400 | this.cellOver(cell); | ||
2401 | } | ||
2402 | } | ||
2403 | }, | ||
2404 | |||
2405 | |||
2406 | // Called when dragging has been stopped | ||
2407 | dragStop: function() { | ||
2408 | this.cellDone(); | ||
2409 | DragListener.prototype.dragStop.apply(this, arguments); // call the super-method | ||
2410 | }, | ||
2411 | |||
2412 | |||
2413 | // Called when a the mouse has just moved over a new cell | ||
2414 | cellOver: function(cell) { | ||
2415 | this.cell = cell; | ||
2416 | this.trigger('cellOver', cell, isCellsEqual(cell, this.origCell), this.origCell); | ||
2417 | }, | ||
2418 | |||
2419 | |||
2420 | // Called when the mouse has just moved out of a cell | ||
2421 | cellOut: function() { | ||
2422 | if (this.cell) { | ||
2423 | this.trigger('cellOut', this.cell); | ||
2424 | this.cellDone(); | ||
2425 | this.cell = null; | ||
2426 | } | ||
2427 | }, | ||
2428 | |||
2429 | |||
2430 | // Called after a cellOut. Also called before a dragStop | ||
2431 | cellDone: function() { | ||
2432 | if (this.cell) { | ||
2433 | this.trigger('cellDone', this.cell); | ||
2434 | } | ||
2435 | }, | ||
2436 | |||
2437 | |||
2438 | // Called when drag listening has stopped | ||
2439 | listenStop: function() { | ||
2440 | DragListener.prototype.listenStop.apply(this, arguments); // call the super-method | ||
2441 | |||
2442 | this.origCell = this.cell = null; | ||
2443 | this.coordMap.clear(); | ||
2444 | }, | ||
2445 | |||
2446 | |||
2447 | // Called when scrolling has stopped, whether through auto scroll, or the user scrolling | ||
2448 | scrollStop: function() { | ||
2449 | DragListener.prototype.scrollStop.apply(this, arguments); // call the super-method | ||
2450 | |||
2451 | this.computeCoords(); // cells' absolute positions will be in new places. recompute | ||
2452 | }, | ||
2453 | |||
2454 | |||
2455 | // Gets the cell underneath the coordinates for the given mouse event | ||
2456 | getCell: function(left, top) { | ||
2457 | |||
2458 | if (this.coordAdjust) { | ||
2459 | left += this.coordAdjust.left; | ||
2460 | top += this.coordAdjust.top; | ||
2461 | } | ||
2462 | |||
2463 | return this.coordMap.getCell(left, top); | ||
2464 | } | ||
2465 | |||
2466 | }); | ||
2467 | |||
2468 | |||
2469 | // Returns `true` if the cells are identically equal. `false` otherwise. | ||
2470 | // They must have the same row, col, and be from the same grid. | ||
2471 | // Two null values will be considered equal, as two "out of the grid" states are the same. | ||
2472 | function isCellsEqual(cell1, cell2) { | ||
2473 | |||
2474 | if (!cell1 && !cell2) { | ||
2475 | return true; | ||
2476 | } | ||
2477 | |||
2478 | if (cell1 && cell2) { | ||
2479 | return cell1.grid === cell2.grid && | ||
2480 | cell1.row === cell2.row && | ||
2481 | cell1.col === cell2.col; | ||
2482 | } | ||
2483 | |||
2484 | return false; | ||
2485 | } | ||
2486 | |||
2487 | ;; | ||
2488 | |||
2489 | /* Creates a clone of an element and lets it track the mouse as it moves | ||
2490 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
2491 | |||
2492 | var MouseFollower = Class.extend({ | ||
2493 | |||
2494 | options: null, | ||
2495 | |||
2496 | sourceEl: null, // the element that will be cloned and made to look like it is dragging | ||
2497 | el: null, // the clone of `sourceEl` that will track the mouse | ||
2498 | parentEl: null, // the element that `el` (the clone) will be attached to | ||
2499 | |||
2500 | // the initial position of el, relative to the offset parent. made to match the initial offset of sourceEl | ||
2501 | top0: null, | ||
2502 | left0: null, | ||
2503 | |||
2504 | // the initial position of the mouse | ||
2505 | mouseY0: null, | ||
2506 | mouseX0: null, | ||
2507 | |||
2508 | // the number of pixels the mouse has moved from its initial position | ||
2509 | topDelta: null, | ||
2510 | leftDelta: null, | ||
2511 | |||
2512 | mousemoveProxy: null, // document mousemove handler, bound to the MouseFollower's `this` | ||
2513 | |||
2514 | isFollowing: false, | ||
2515 | isHidden: false, | ||
2516 | isAnimating: false, // doing the revert animation? | ||
2517 | |||
2518 | constructor: function(sourceEl, options) { | ||
2519 | this.options = options = options || {}; | ||
2520 | this.sourceEl = sourceEl; | ||
2521 | this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent | ||
2522 | }, | ||
2523 | |||
2524 | |||
2525 | // Causes the element to start following the mouse | ||
2526 | start: function(ev) { | ||
2527 | if (!this.isFollowing) { | ||
2528 | this.isFollowing = true; | ||
2529 | |||
2530 | this.mouseY0 = ev.pageY; | ||
2531 | this.mouseX0 = ev.pageX; | ||
2532 | this.topDelta = 0; | ||
2533 | this.leftDelta = 0; | ||
2534 | |||
2535 | if (!this.isHidden) { | ||
2536 | this.updatePosition(); | ||
2537 | } | ||
2538 | |||
2539 | $(document).on('mousemove', this.mousemoveProxy = proxy(this, 'mousemove')); | ||
2540 | } | ||
2541 | }, | ||
2542 | |||
2543 | |||
2544 | // Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position. | ||
2545 | // `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately. | ||
2546 | stop: function(shouldRevert, callback) { | ||
2547 | var _this = this; | ||
2548 | var revertDuration = this.options.revertDuration; | ||
2549 | |||
2550 | function complete() { | ||
2551 | this.isAnimating = false; | ||
2552 | _this.destroyEl(); | ||
2553 | |||
2554 | this.top0 = this.left0 = null; // reset state for future updatePosition calls | ||
2555 | |||
2556 | if (callback) { | ||
2557 | callback(); | ||
2558 | } | ||
2559 | } | ||
2560 | |||
2561 | if (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time | ||
2562 | this.isFollowing = false; | ||
2563 | |||
2564 | $(document).off('mousemove', this.mousemoveProxy); | ||
2565 | |||
2566 | if (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation? | ||
2567 | this.isAnimating = true; | ||
2568 | this.el.animate({ | ||
2569 | top: this.top0, | ||
2570 | left: this.left0 | ||
2571 | }, { | ||
2572 | duration: revertDuration, | ||
2573 | complete: complete | ||
2574 | }); | ||
2575 | } | ||
2576 | else { | ||
2577 | complete(); | ||
2578 | } | ||
2579 | } | ||
2580 | }, | ||
2581 | |||
2582 | |||
2583 | // Gets the tracking element. Create it if necessary | ||
2584 | getEl: function() { | ||
2585 | var el = this.el; | ||
2586 | |||
2587 | if (!el) { | ||
2588 | this.sourceEl.width(); // hack to force IE8 to compute correct bounding box | ||
2589 | el = this.el = this.sourceEl.clone() | ||
2590 | .css({ | ||
2591 | position: 'absolute', | ||
2592 | visibility: '', // in case original element was hidden (commonly through hideEvents()) | ||
2593 | display: this.isHidden ? 'none' : '', // for when initially hidden | ||
2594 | margin: 0, | ||
2595 | right: 'auto', // erase and set width instead | ||
2596 | bottom: 'auto', // erase and set height instead | ||
2597 | width: this.sourceEl.width(), // explicit height in case there was a 'right' value | ||
2598 | height: this.sourceEl.height(), // explicit width in case there was a 'bottom' value | ||
2599 | opacity: this.options.opacity || '', | ||
2600 | zIndex: this.options.zIndex | ||
2601 | }) | ||
2602 | .appendTo(this.parentEl); | ||
2603 | } | ||
2604 | |||
2605 | return el; | ||
2606 | }, | ||
2607 | |||
2608 | |||
2609 | // Removes the tracking element if it has already been created | ||
2610 | destroyEl: function() { | ||
2611 | if (this.el) { | ||
2612 | this.el.remove(); | ||
2613 | this.el = null; | ||
2614 | } | ||
2615 | }, | ||
2616 | |||
2617 | |||
2618 | // Update the CSS position of the tracking element | ||
2619 | updatePosition: function() { | ||
2620 | var sourceOffset; | ||
2621 | var origin; | ||
2622 | |||
2623 | this.getEl(); // ensure this.el | ||
2624 | |||
2625 | // make sure origin info was computed | ||
2626 | if (this.top0 === null) { | ||
2627 | this.sourceEl.width(); // hack to force IE8 to compute correct bounding box | ||
2628 | sourceOffset = this.sourceEl.offset(); | ||
2629 | origin = this.el.offsetParent().offset(); | ||
2630 | this.top0 = sourceOffset.top - origin.top; | ||
2631 | this.left0 = sourceOffset.left - origin.left; | ||
2632 | } | ||
2633 | |||
2634 | this.el.css({ | ||
2635 | top: this.top0 + this.topDelta, | ||
2636 | left: this.left0 + this.leftDelta | ||
2637 | }); | ||
2638 | }, | ||
2639 | |||
2640 | |||
2641 | // Gets called when the user moves the mouse | ||
2642 | mousemove: function(ev) { | ||
2643 | this.topDelta = ev.pageY - this.mouseY0; | ||
2644 | this.leftDelta = ev.pageX - this.mouseX0; | ||
2645 | |||
2646 | if (!this.isHidden) { | ||
2647 | this.updatePosition(); | ||
2648 | } | ||
2649 | }, | ||
2650 | |||
2651 | |||
2652 | // Temporarily makes the tracking element invisible. Can be called before following starts | ||
2653 | hide: function() { | ||
2654 | if (!this.isHidden) { | ||
2655 | this.isHidden = true; | ||
2656 | if (this.el) { | ||
2657 | this.el.hide(); | ||
2658 | } | ||
2659 | } | ||
2660 | }, | ||
2661 | |||
2662 | |||
2663 | // Show the tracking element after it has been temporarily hidden | ||
2664 | show: function() { | ||
2665 | if (this.isHidden) { | ||
2666 | this.isHidden = false; | ||
2667 | this.updatePosition(); | ||
2668 | this.getEl().show(); | ||
2669 | } | ||
2670 | } | ||
2671 | |||
2672 | }); | ||
2673 | |||
2674 | ;; | ||
2675 | |||
2676 | /* A utility class for rendering <tr> rows. | ||
2677 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
2678 | // It leverages methods of the subclass and the View to determine custom rendering behavior for each row "type" | ||
2679 | // (such as highlight rows, day rows, helper rows, etc). | ||
2680 | |||
2681 | var RowRenderer = Class.extend({ | ||
2682 | |||
2683 | view: null, // a View object | ||
2684 | isRTL: null, // shortcut to the view's isRTL option | ||
2685 | cellHtml: '<td/>', // plain default HTML used for a cell when no other is available | ||
2686 | |||
2687 | |||
2688 | constructor: function(view) { | ||
2689 | this.view = view; | ||
2690 | this.isRTL = view.opt('isRTL'); | ||
2691 | }, | ||
2692 | |||
2693 | |||
2694 | // Renders the HTML for a row, leveraging custom cell-HTML-renderers based on the `rowType`. | ||
2695 | // Also applies the "intro" and "outro" cells, which are specified by the subclass and views. | ||
2696 | // `row` is an optional row number. | ||
2697 | rowHtml: function(rowType, row) { | ||
2698 | var renderCell = this.getHtmlRenderer('cell', rowType); | ||
2699 | var rowCellHtml = ''; | ||
2700 | var col; | ||
2701 | var cell; | ||
2702 | |||
2703 | row = row || 0; | ||
2704 | |||
2705 | for (col = 0; col < this.colCnt; col++) { | ||
2706 | cell = this.getCell(row, col); | ||
2707 | rowCellHtml += renderCell(cell); | ||
2708 | } | ||
2709 | |||
2710 | rowCellHtml = this.bookendCells(rowCellHtml, rowType, row); // apply intro and outro | ||
2711 | |||
2712 | return '<tr>' + rowCellHtml + '</tr>'; | ||
2713 | }, | ||
2714 | |||
2715 | |||
2716 | // Applies the "intro" and "outro" HTML to the given cells. | ||
2717 | // Intro means the leftmost cell when the calendar is LTR and the rightmost cell when RTL. Vice-versa for outro. | ||
2718 | // `cells` can be an HTML string of <td>'s or a jQuery <tr> element | ||
2719 | // `row` is an optional row number. | ||
2720 | bookendCells: function(cells, rowType, row) { | ||
2721 | var intro = this.getHtmlRenderer('intro', rowType)(row || 0); | ||
2722 | var outro = this.getHtmlRenderer('outro', rowType)(row || 0); | ||
2723 | var prependHtml = this.isRTL ? outro : intro; | ||
2724 | var appendHtml = this.isRTL ? intro : outro; | ||
2725 | |||
2726 | if (typeof cells === 'string') { | ||
2727 | return prependHtml + cells + appendHtml; | ||
2728 | } | ||
2729 | else { // a jQuery <tr> element | ||
2730 | return cells.prepend(prependHtml).append(appendHtml); | ||
2731 | } | ||
2732 | }, | ||
2733 | |||
2734 | |||
2735 | // Returns an HTML-rendering function given a specific `rendererName` (like cell, intro, or outro) and a specific | ||
2736 | // `rowType` (like day, eventSkeleton, helperSkeleton), which is optional. | ||
2737 | // If a renderer for the specific rowType doesn't exist, it will fall back to a generic renderer. | ||
2738 | // We will query the View object first for any custom rendering functions, then the methods of the subclass. | ||
2739 | getHtmlRenderer: function(rendererName, rowType) { | ||
2740 | var view = this.view; | ||
2741 | var generalName; // like "cellHtml" | ||
2742 | var specificName; // like "dayCellHtml". based on rowType | ||
2743 | var provider; // either the View or the RowRenderer subclass, whichever provided the method | ||
2744 | var renderer; | ||
2745 | |||
2746 | generalName = rendererName + 'Html'; | ||
2747 | if (rowType) { | ||
2748 | specificName = rowType + capitaliseFirstLetter(rendererName) + 'Html'; | ||
2749 | } | ||
2750 | |||
2751 | if (specificName && (renderer = view[specificName])) { | ||
2752 | provider = view; | ||
2753 | } | ||
2754 | else if (specificName && (renderer = this[specificName])) { | ||
2755 | provider = this; | ||
2756 | } | ||
2757 | else if ((renderer = view[generalName])) { | ||
2758 | provider = view; | ||
2759 | } | ||
2760 | else if ((renderer = this[generalName])) { | ||
2761 | provider = this; | ||
2762 | } | ||
2763 | |||
2764 | if (typeof renderer === 'function') { | ||
2765 | return function() { | ||
2766 | return renderer.apply(provider, arguments) || ''; // use correct `this` and always return a string | ||
2767 | }; | ||
2768 | } | ||
2769 | |||
2770 | // the rendered can be a plain string as well. if not specified, always an empty string. | ||
2771 | return function() { | ||
2772 | return renderer || ''; | ||
2773 | }; | ||
2774 | } | ||
2775 | |||
2776 | }); | ||
2777 | |||
2778 | ;; | ||
2779 | |||
2780 | /* An abstract class comprised of a "grid" of cells that each represent a specific datetime | ||
2781 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
2782 | |||
2783 | var Grid = fc.Grid = RowRenderer.extend({ | ||
2784 | |||
2785 | start: null, // the date of the first cell | ||
2786 | end: null, // the date after the last cell | ||
2787 | |||
2788 | rowCnt: 0, // number of rows | ||
2789 | colCnt: 0, // number of cols | ||
2790 | rowData: null, // array of objects, holding misc data for each row | ||
2791 | colData: null, // array of objects, holding misc data for each column | ||
2792 | |||
2793 | el: null, // the containing element | ||
2794 | coordMap: null, // a GridCoordMap that converts pixel values to datetimes | ||
2795 | elsByFill: null, // a hash of jQuery element sets used for rendering each fill. Keyed by fill name. | ||
2796 | |||
2797 | externalDragStartProxy: null, // binds the Grid's scope to externalDragStart (in DayGrid.events) | ||
2798 | |||
2799 | // derived from options | ||
2800 | colHeadFormat: null, // TODO: move to another class. not applicable to all Grids | ||
2801 | eventTimeFormat: null, | ||
2802 | displayEventTime: null, | ||
2803 | displayEventEnd: null, | ||
2804 | |||
2805 | // if all cells are the same length of time, the duration they all share. optional. | ||
2806 | // when defined, allows the computeCellRange shortcut, as well as improved resizing behavior. | ||
2807 | cellDuration: null, | ||
2808 | |||
2809 | // if defined, holds the unit identified (ex: "year" or "month") that determines the level of granularity | ||
2810 | // of the date cells. if not defined, assumes to be day and time granularity. | ||
2811 | largeUnit: null, | ||
2812 | |||
2813 | |||
2814 | constructor: function() { | ||
2815 | RowRenderer.apply(this, arguments); // call the super-constructor | ||
2816 | |||
2817 | this.coordMap = new GridCoordMap(this); | ||
2818 | this.elsByFill = {}; | ||
2819 | this.externalDragStartProxy = proxy(this, 'externalDragStart'); | ||
2820 | }, | ||
2821 | |||
2822 | |||
2823 | /* Options | ||
2824 | ------------------------------------------------------------------------------------------------------------------*/ | ||
2825 | |||
2826 | |||
2827 | // Generates the format string used for the text in column headers, if not explicitly defined by 'columnFormat' | ||
2828 | // TODO: move to another class. not applicable to all Grids | ||
2829 | computeColHeadFormat: function() { | ||
2830 | // subclasses must implement if they want to use headHtml() | ||
2831 | }, | ||
2832 | |||
2833 | |||
2834 | // Generates the format string used for event time text, if not explicitly defined by 'timeFormat' | ||
2835 | computeEventTimeFormat: function() { | ||
2836 | return this.view.opt('smallTimeFormat'); | ||
2837 | }, | ||
2838 | |||
2839 | |||
2840 | // Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventTime'. | ||
2841 | // Only applies to non-all-day events. | ||
2842 | computeDisplayEventTime: function() { | ||
2843 | return true; | ||
2844 | }, | ||
2845 | |||
2846 | |||
2847 | // Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventEnd' | ||
2848 | computeDisplayEventEnd: function() { | ||
2849 | return true; | ||
2850 | }, | ||
2851 | |||
2852 | |||
2853 | /* Dates | ||
2854 | ------------------------------------------------------------------------------------------------------------------*/ | ||
2855 | |||
2856 | |||
2857 | // Tells the grid about what period of time to display. Grid will subsequently compute dates for cell system. | ||
2858 | setRange: function(range) { | ||
2859 | var view = this.view; | ||
2860 | var displayEventTime; | ||
2861 | var displayEventEnd; | ||
2862 | |||
2863 | this.start = range.start.clone(); | ||
2864 | this.end = range.end.clone(); | ||
2865 | |||
2866 | this.rowData = []; | ||
2867 | this.colData = []; | ||
2868 | this.updateCells(); | ||
2869 | |||
2870 | // Populate option-derived settings. Look for override first, then compute if necessary. | ||
2871 | this.colHeadFormat = view.opt('columnFormat') || this.computeColHeadFormat(); | ||
2872 | |||
2873 | this.eventTimeFormat = | ||
2874 | view.opt('eventTimeFormat') || | ||
2875 | view.opt('timeFormat') || // deprecated | ||
2876 | this.computeEventTimeFormat(); | ||
2877 | |||
2878 | displayEventTime = view.opt('displayEventTime'); | ||
2879 | if (displayEventTime == null) { | ||
2880 | displayEventTime = this.computeDisplayEventTime(); // might be based off of range | ||
2881 | } | ||
2882 | |||
2883 | displayEventEnd = view.opt('displayEventEnd'); | ||
2884 | if (displayEventEnd == null) { | ||
2885 | displayEventEnd = this.computeDisplayEventEnd(); // might be based off of range | ||
2886 | } | ||
2887 | |||
2888 | this.displayEventTime = displayEventTime; | ||
2889 | this.displayEventEnd = displayEventEnd; | ||
2890 | }, | ||
2891 | |||
2892 | |||
2893 | // Responsible for setting rowCnt/colCnt and any other row/col data | ||
2894 | updateCells: function() { | ||
2895 | // subclasses must implement | ||
2896 | }, | ||
2897 | |||
2898 | |||
2899 | // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects | ||
2900 | rangeToSegs: function(range) { | ||
2901 | // subclasses must implement | ||
2902 | }, | ||
2903 | |||
2904 | |||
2905 | // Diffs the two dates, returning a duration, based on granularity of the grid | ||
2906 | diffDates: function(a, b) { | ||
2907 | if (this.largeUnit) { | ||
2908 | return diffByUnit(a, b, this.largeUnit); | ||
2909 | } | ||
2910 | else { | ||
2911 | return diffDayTime(a, b); | ||
2912 | } | ||
2913 | }, | ||
2914 | |||
2915 | |||
2916 | /* Cells | ||
2917 | ------------------------------------------------------------------------------------------------------------------*/ | ||
2918 | // NOTE: columns are ordered left-to-right | ||
2919 | |||
2920 | |||
2921 | // Gets an object containing row/col number, misc data, and range information about the cell. | ||
2922 | // Accepts row/col values, an object with row/col properties, or a single-number offset from the first cell. | ||
2923 | getCell: function(row, col) { | ||
2924 | var cell; | ||
2925 | |||
2926 | if (col == null) { | ||
2927 | if (typeof row === 'number') { // a single-number offset | ||
2928 | col = row % this.colCnt; | ||
2929 | row = Math.floor(row / this.colCnt); | ||
2930 | } | ||
2931 | else { // an object with row/col properties | ||
2932 | col = row.col; | ||
2933 | row = row.row; | ||
2934 | } | ||
2935 | } | ||
2936 | |||
2937 | cell = { row: row, col: col }; | ||
2938 | |||
2939 | $.extend(cell, this.getRowData(row), this.getColData(col)); | ||
2940 | $.extend(cell, this.computeCellRange(cell)); | ||
2941 | |||
2942 | return cell; | ||
2943 | }, | ||
2944 | |||
2945 | |||
2946 | // Given a cell object with index and misc data, generates a range object | ||
2947 | // If the grid is leveraging cellDuration, this doesn't need to be defined. Only computeCellDate does. | ||
2948 | // If being overridden, should return a range with reference-free date copies. | ||
2949 | computeCellRange: function(cell) { | ||
2950 | var date = this.computeCellDate(cell); | ||
2951 | |||
2952 | return { | ||
2953 | start: date, | ||
2954 | end: date.clone().add(this.cellDuration) | ||
2955 | }; | ||
2956 | }, | ||
2957 | |||
2958 | |||
2959 | // Given a cell, returns its start date. Should return a reference-free date copy. | ||
2960 | computeCellDate: function(cell) { | ||
2961 | // subclasses can implement | ||
2962 | }, | ||
2963 | |||
2964 | |||
2965 | // Retrieves misc data about the given row | ||
2966 | getRowData: function(row) { | ||
2967 | return this.rowData[row] || {}; | ||
2968 | }, | ||
2969 | |||
2970 | |||
2971 | // Retrieves misc data baout the given column | ||
2972 | getColData: function(col) { | ||
2973 | return this.colData[col] || {}; | ||
2974 | }, | ||
2975 | |||
2976 | |||
2977 | // Retrieves the element representing the given row | ||
2978 | getRowEl: function(row) { | ||
2979 | // subclasses should implement if leveraging the default getCellDayEl() or computeRowCoords() | ||
2980 | }, | ||
2981 | |||
2982 | |||
2983 | // Retrieves the element representing the given column | ||
2984 | getColEl: function(col) { | ||
2985 | // subclasses should implement if leveraging the default getCellDayEl() or computeColCoords() | ||
2986 | }, | ||
2987 | |||
2988 | |||
2989 | // Given a cell object, returns the element that represents the cell's whole-day | ||
2990 | getCellDayEl: function(cell) { | ||
2991 | return this.getColEl(cell.col) || this.getRowEl(cell.row); | ||
2992 | }, | ||
2993 | |||
2994 | |||
2995 | /* Cell Coordinates | ||
2996 | ------------------------------------------------------------------------------------------------------------------*/ | ||
2997 | |||
2998 | |||
2999 | // Computes the top/bottom coordinates of all rows. | ||
3000 | // By default, queries the dimensions of the element provided by getRowEl(). | ||
3001 | computeRowCoords: function() { | ||
3002 | var items = []; | ||
3003 | var i, el; | ||
3004 | var top; | ||
3005 | |||
3006 | for (i = 0; i < this.rowCnt; i++) { | ||
3007 | el = this.getRowEl(i); | ||
3008 | top = el.offset().top; | ||
3009 | items.push({ | ||
3010 | top: top, | ||
3011 | bottom: top + el.outerHeight() | ||
3012 | }); | ||
3013 | } | ||
3014 | |||
3015 | return items; | ||
3016 | }, | ||
3017 | |||
3018 | |||
3019 | // Computes the left/right coordinates of all rows. | ||
3020 | // By default, queries the dimensions of the element provided by getColEl(). Columns can be LTR or RTL. | ||
3021 | computeColCoords: function() { | ||
3022 | var items = []; | ||
3023 | var i, el; | ||
3024 | var left; | ||
3025 | |||
3026 | for (i = 0; i < this.colCnt; i++) { | ||
3027 | el = this.getColEl(i); | ||
3028 | left = el.offset().left; | ||
3029 | items.push({ | ||
3030 | left: left, | ||
3031 | right: left + el.outerWidth() | ||
3032 | }); | ||
3033 | } | ||
3034 | |||
3035 | return items; | ||
3036 | }, | ||
3037 | |||
3038 | |||
3039 | /* Rendering | ||
3040 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3041 | |||
3042 | |||
3043 | // Sets the container element that the grid should render inside of. | ||
3044 | // Does other DOM-related initializations. | ||
3045 | setElement: function(el) { | ||
3046 | var _this = this; | ||
3047 | |||
3048 | this.el = el; | ||
3049 | |||
3050 | // attach a handler to the grid's root element. | ||
3051 | // jQuery will take care of unregistering them when removeElement gets called. | ||
3052 | el.on('mousedown', function(ev) { | ||
3053 | if ( | ||
3054 | !$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or "more.." link | ||
3055 | !$(ev.target).closest('.fc-popover').length // not on a popover (like the "more.." events one) | ||
3056 | ) { | ||
3057 | _this.dayMousedown(ev); | ||
3058 | } | ||
3059 | }); | ||
3060 | |||
3061 | // attach event-element-related handlers. in Grid.events | ||
3062 | // same garbage collection note as above. | ||
3063 | this.bindSegHandlers(); | ||
3064 | |||
3065 | this.bindGlobalHandlers(); | ||
3066 | }, | ||
3067 | |||
3068 | |||
3069 | // Removes the grid's container element from the DOM. Undoes any other DOM-related attachments. | ||
3070 | // DOES NOT remove any content before hand (doens't clear events or call destroyDates), unlike View | ||
3071 | removeElement: function() { | ||
3072 | this.unbindGlobalHandlers(); | ||
3073 | |||
3074 | this.el.remove(); | ||
3075 | |||
3076 | // NOTE: we don't null-out this.el for the same reasons we don't do it within View::removeElement | ||
3077 | }, | ||
3078 | |||
3079 | |||
3080 | // Renders the basic structure of grid view before any content is rendered | ||
3081 | renderSkeleton: function() { | ||
3082 | // subclasses should implement | ||
3083 | }, | ||
3084 | |||
3085 | |||
3086 | // Renders the grid's date-related content (like cells that represent days/times). | ||
3087 | // Assumes setRange has already been called and the skeleton has already been rendered. | ||
3088 | renderDates: function() { | ||
3089 | // subclasses should implement | ||
3090 | }, | ||
3091 | |||
3092 | |||
3093 | // Unrenders the grid's date-related content | ||
3094 | destroyDates: function() { | ||
3095 | // subclasses should implement | ||
3096 | }, | ||
3097 | |||
3098 | |||
3099 | /* Handlers | ||
3100 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3101 | |||
3102 | |||
3103 | // Binds DOM handlers to elements that reside outside the grid, such as the document | ||
3104 | bindGlobalHandlers: function() { | ||
3105 | $(document).on('dragstart sortstart', this.externalDragStartProxy); // jqui | ||
3106 | }, | ||
3107 | |||
3108 | |||
3109 | // Unbinds DOM handlers from elements that reside outside the grid | ||
3110 | unbindGlobalHandlers: function() { | ||
3111 | $(document).off('dragstart sortstart', this.externalDragStartProxy); // jqui | ||
3112 | }, | ||
3113 | |||
3114 | |||
3115 | // Process a mousedown on an element that represents a day. For day clicking and selecting. | ||
3116 | dayMousedown: function(ev) { | ||
3117 | var _this = this; | ||
3118 | var view = this.view; | ||
3119 | var isSelectable = view.opt('selectable'); | ||
3120 | var dayClickCell; // null if invalid dayClick | ||
3121 | var selectionRange; // null if invalid selection | ||
3122 | |||
3123 | // this listener tracks a mousedown on a day element, and a subsequent drag. | ||
3124 | // if the drag ends on the same day, it is a 'dayClick'. | ||
3125 | // if 'selectable' is enabled, this listener also detects selections. | ||
3126 | var dragListener = new CellDragListener(this.coordMap, { | ||
3127 | //distance: 5, // needs more work if we want dayClick to fire correctly | ||
3128 | scroll: view.opt('dragScroll'), | ||
3129 | dragStart: function() { | ||
3130 | view.unselect(); // since we could be rendering a new selection, we want to clear any old one | ||
3131 | }, | ||
3132 | cellOver: function(cell, isOrig, origCell) { | ||
3133 | if (origCell) { // click needs to have started on a cell | ||
3134 | dayClickCell = isOrig ? cell : null; // single-cell selection is a day click | ||
3135 | if (isSelectable) { | ||
3136 | selectionRange = _this.computeSelection(origCell, cell); | ||
3137 | if (selectionRange) { | ||
3138 | _this.renderSelection(selectionRange); | ||
3139 | } | ||
3140 | else { | ||
3141 | disableCursor(); | ||
3142 | } | ||
3143 | } | ||
3144 | } | ||
3145 | }, | ||
3146 | cellOut: function(cell) { | ||
3147 | dayClickCell = null; | ||
3148 | selectionRange = null; | ||
3149 | _this.destroySelection(); | ||
3150 | enableCursor(); | ||
3151 | }, | ||
3152 | listenStop: function(ev) { | ||
3153 | if (dayClickCell) { | ||
3154 | view.trigger('dayClick', _this.getCellDayEl(dayClickCell), dayClickCell.start, ev); | ||
3155 | } | ||
3156 | if (selectionRange) { | ||
3157 | // the selection will already have been rendered. just report it | ||
3158 | view.reportSelection(selectionRange, ev); | ||
3159 | } | ||
3160 | enableCursor(); | ||
3161 | } | ||
3162 | }); | ||
3163 | |||
3164 | dragListener.mousedown(ev); // start listening, which will eventually initiate a dragStart | ||
3165 | }, | ||
3166 | |||
3167 | |||
3168 | /* Event Helper | ||
3169 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3170 | // TODO: should probably move this to Grid.events, like we did event dragging / resizing | ||
3171 | |||
3172 | |||
3173 | // Renders a mock event over the given range | ||
3174 | renderRangeHelper: function(range, sourceSeg) { | ||
3175 | var fakeEvent = this.fabricateHelperEvent(range, sourceSeg); | ||
3176 | |||
3177 | this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering | ||
3178 | }, | ||
3179 | |||
3180 | |||
3181 | // Builds a fake event given a date range it should cover, and a segment is should be inspired from. | ||
3182 | // The range's end can be null, in which case the mock event that is rendered will have a null end time. | ||
3183 | // `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging. | ||
3184 | fabricateHelperEvent: function(range, sourceSeg) { | ||
3185 | var fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible | ||
3186 | |||
3187 | fakeEvent.start = range.start.clone(); | ||
3188 | fakeEvent.end = range.end ? range.end.clone() : null; | ||
3189 | fakeEvent.allDay = null; // force it to be freshly computed by normalizeEventRange | ||
3190 | this.view.calendar.normalizeEventRange(fakeEvent); | ||
3191 | |||
3192 | // this extra className will be useful for differentiating real events from mock events in CSS | ||
3193 | fakeEvent.className = (fakeEvent.className || []).concat('fc-helper'); | ||
3194 | |||
3195 | // if something external is being dragged in, don't render a resizer | ||
3196 | if (!sourceSeg) { | ||
3197 | fakeEvent.editable = false; | ||
3198 | } | ||
3199 | |||
3200 | return fakeEvent; | ||
3201 | }, | ||
3202 | |||
3203 | |||
3204 | // Renders a mock event | ||
3205 | renderHelper: function(event, sourceSeg) { | ||
3206 | // subclasses must implement | ||
3207 | }, | ||
3208 | |||
3209 | |||
3210 | // Unrenders a mock event | ||
3211 | destroyHelper: function() { | ||
3212 | // subclasses must implement | ||
3213 | }, | ||
3214 | |||
3215 | |||
3216 | /* Selection | ||
3217 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3218 | |||
3219 | |||
3220 | // Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses. | ||
3221 | renderSelection: function(range) { | ||
3222 | this.renderHighlight(range); | ||
3223 | }, | ||
3224 | |||
3225 | |||
3226 | // Unrenders any visual indications of a selection. Will unrender a highlight by default. | ||
3227 | destroySelection: function() { | ||
3228 | this.destroyHighlight(); | ||
3229 | }, | ||
3230 | |||
3231 | |||
3232 | // Given the first and last cells of a selection, returns a range object. | ||
3233 | // Will return something falsy if the selection is invalid (when outside of selectionConstraint for example). | ||
3234 | // Subclasses can override and provide additional data in the range object. Will be passed to renderSelection(). | ||
3235 | computeSelection: function(firstCell, lastCell) { | ||
3236 | var dates = [ | ||
3237 | firstCell.start, | ||
3238 | firstCell.end, | ||
3239 | lastCell.start, | ||
3240 | lastCell.end | ||
3241 | ]; | ||
3242 | var range; | ||
3243 | |||
3244 | dates.sort(compareNumbers); // sorts chronologically. works with Moments | ||
3245 | |||
3246 | range = { | ||
3247 | start: dates[0].clone(), | ||
3248 | end: dates[3].clone() | ||
3249 | }; | ||
3250 | |||
3251 | if (!this.view.calendar.isSelectionRangeAllowed(range)) { | ||
3252 | return null; | ||
3253 | } | ||
3254 | |||
3255 | return range; | ||
3256 | }, | ||
3257 | |||
3258 | |||
3259 | /* Highlight | ||
3260 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3261 | |||
3262 | |||
3263 | // Renders an emphasis on the given date range. `start` is inclusive. `end` is exclusive. | ||
3264 | renderHighlight: function(range) { | ||
3265 | this.renderFill('highlight', this.rangeToSegs(range)); | ||
3266 | }, | ||
3267 | |||
3268 | |||
3269 | // Unrenders the emphasis on a date range | ||
3270 | destroyHighlight: function() { | ||
3271 | this.destroyFill('highlight'); | ||
3272 | }, | ||
3273 | |||
3274 | |||
3275 | // Generates an array of classNames for rendering the highlight. Used by the fill system. | ||
3276 | highlightSegClasses: function() { | ||
3277 | return [ 'fc-highlight' ]; | ||
3278 | }, | ||
3279 | |||
3280 | |||
3281 | /* Fill System (highlight, background events, business hours) | ||
3282 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3283 | |||
3284 | |||
3285 | // Renders a set of rectangles over the given segments of time. | ||
3286 | // Returns a subset of segs, the segs that were actually rendered. | ||
3287 | // Responsible for populating this.elsByFill. TODO: better API for expressing this requirement | ||
3288 | renderFill: function(type, segs) { | ||
3289 | // subclasses must implement | ||
3290 | }, | ||
3291 | |||
3292 | |||
3293 | // Unrenders a specific type of fill that is currently rendered on the grid | ||
3294 | destroyFill: function(type) { | ||
3295 | var el = this.elsByFill[type]; | ||
3296 | |||
3297 | if (el) { | ||
3298 | el.remove(); | ||
3299 | delete this.elsByFill[type]; | ||
3300 | } | ||
3301 | }, | ||
3302 | |||
3303 | |||
3304 | // Renders and assigns an `el` property for each fill segment. Generic enough to work with different types. | ||
3305 | // Only returns segments that successfully rendered. | ||
3306 | // To be harnessed by renderFill (implemented by subclasses). | ||
3307 | // Analagous to renderFgSegEls. | ||
3308 | renderFillSegEls: function(type, segs) { | ||
3309 | var _this = this; | ||
3310 | var segElMethod = this[type + 'SegEl']; | ||
3311 | var html = ''; | ||
3312 | var renderedSegs = []; | ||
3313 | var i; | ||
3314 | |||
3315 | if (segs.length) { | ||
3316 | |||
3317 | // build a large concatenation of segment HTML | ||
3318 | for (i = 0; i < segs.length; i++) { | ||
3319 | html += this.fillSegHtml(type, segs[i]); | ||
3320 | } | ||
3321 | |||
3322 | // Grab individual elements from the combined HTML string. Use each as the default rendering. | ||
3323 | // Then, compute the 'el' for each segment. | ||
3324 | $(html).each(function(i, node) { | ||
3325 | var seg = segs[i]; | ||
3326 | var el = $(node); | ||
3327 | |||
3328 | // allow custom filter methods per-type | ||
3329 | if (segElMethod) { | ||
3330 | el = segElMethod.call(_this, seg, el); | ||
3331 | } | ||
3332 | |||
3333 | if (el) { // custom filters did not cancel the render | ||
3334 | el = $(el); // allow custom filter to return raw DOM node | ||
3335 | |||
3336 | // correct element type? (would be bad if a non-TD were inserted into a table for example) | ||
3337 | if (el.is(_this.fillSegTag)) { | ||
3338 | seg.el = el; | ||
3339 | renderedSegs.push(seg); | ||
3340 | } | ||
3341 | } | ||
3342 | }); | ||
3343 | } | ||
3344 | |||
3345 | return renderedSegs; | ||
3346 | }, | ||
3347 | |||
3348 | |||
3349 | fillSegTag: 'div', // subclasses can override | ||
3350 | |||
3351 | |||
3352 | // Builds the HTML needed for one fill segment. Generic enought o work with different types. | ||
3353 | fillSegHtml: function(type, seg) { | ||
3354 | |||
3355 | // custom hooks per-type | ||
3356 | var classesMethod = this[type + 'SegClasses']; | ||
3357 | var cssMethod = this[type + 'SegCss']; | ||
3358 | |||
3359 | var classes = classesMethod ? classesMethod.call(this, seg) : []; | ||
3360 | var css = cssToStr(cssMethod ? cssMethod.call(this, seg) : {}); | ||
3361 | |||
3362 | return '<' + this.fillSegTag + | ||
3363 | (classes.length ? ' class="' + classes.join(' ') + '"' : '') + | ||
3364 | (css ? ' style="' + css + '"' : '') + | ||
3365 | ' />'; | ||
3366 | }, | ||
3367 | |||
3368 | |||
3369 | /* Generic rendering utilities for subclasses | ||
3370 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3371 | |||
3372 | |||
3373 | // Renders a day-of-week header row. | ||
3374 | // TODO: move to another class. not applicable to all Grids | ||
3375 | headHtml: function() { | ||
3376 | return '' + | ||
3377 | '<div class="fc-row ' + this.view.widgetHeaderClass + '">' + | ||
3378 | '<table>' + | ||
3379 | '<thead>' + | ||
3380 | this.rowHtml('head') + // leverages RowRenderer | ||
3381 | '</thead>' + | ||
3382 | '</table>' + | ||
3383 | '</div>'; | ||
3384 | }, | ||
3385 | |||
3386 | |||
3387 | // Used by the `headHtml` method, via RowRenderer, for rendering the HTML of a day-of-week header cell | ||
3388 | // TODO: move to another class. not applicable to all Grids | ||
3389 | headCellHtml: function(cell) { | ||
3390 | var view = this.view; | ||
3391 | var date = cell.start; | ||
3392 | |||
3393 | return '' + | ||
3394 | '<th class="fc-day-header ' + view.widgetHeaderClass + ' fc-' + dayIDs[date.day()] + '">' + | ||
3395 | htmlEscape(date.format(this.colHeadFormat)) + | ||
3396 | '</th>'; | ||
3397 | }, | ||
3398 | |||
3399 | |||
3400 | // Renders the HTML for a single-day background cell | ||
3401 | bgCellHtml: function(cell) { | ||
3402 | var view = this.view; | ||
3403 | var date = cell.start; | ||
3404 | var classes = this.getDayClasses(date); | ||
3405 | |||
3406 | classes.unshift('fc-day', view.widgetContentClass); | ||
3407 | |||
3408 | return '<td class="' + classes.join(' ') + '"' + | ||
3409 | ' data-date="' + date.format('YYYY-MM-DD') + '"' + // if date has a time, won't format it | ||
3410 | '></td>'; | ||
3411 | }, | ||
3412 | |||
3413 | |||
3414 | // Computes HTML classNames for a single-day cell | ||
3415 | getDayClasses: function(date) { | ||
3416 | var view = this.view; | ||
3417 | var today = view.calendar.getNow().stripTime(); | ||
3418 | var classes = [ 'fc-' + dayIDs[date.day()] ]; | ||
3419 | |||
3420 | if ( | ||
3421 | view.intervalDuration.as('months') == 1 && | ||
3422 | date.month() != view.intervalStart.month() | ||
3423 | ) { | ||
3424 | classes.push('fc-other-month'); | ||
3425 | } | ||
3426 | |||
3427 | if (date.isSame(today, 'day')) { | ||
3428 | classes.push( | ||
3429 | 'fc-today', | ||
3430 | view.highlightStateClass | ||
3431 | ); | ||
3432 | } | ||
3433 | else if (date < today) { | ||
3434 | classes.push('fc-past'); | ||
3435 | } | ||
3436 | else { | ||
3437 | classes.push('fc-future'); | ||
3438 | } | ||
3439 | |||
3440 | return classes; | ||
3441 | } | ||
3442 | |||
3443 | }); | ||
3444 | |||
3445 | ;; | ||
3446 | |||
3447 | /* Event-rendering and event-interaction methods for the abstract Grid class | ||
3448 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
3449 | |||
3450 | Grid.mixin({ | ||
3451 | |||
3452 | mousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing | ||
3453 | isDraggingSeg: false, // is a segment being dragged? boolean | ||
3454 | isResizingSeg: false, // is a segment being resized? boolean | ||
3455 | isDraggingExternal: false, // jqui-dragging an external element? boolean | ||
3456 | segs: null, // the event segments currently rendered in the grid | ||
3457 | |||
3458 | |||
3459 | // Renders the given events onto the grid | ||
3460 | renderEvents: function(events) { | ||
3461 | var segs = this.eventsToSegs(events); | ||
3462 | var bgSegs = []; | ||
3463 | var fgSegs = []; | ||
3464 | var i, seg; | ||
3465 | |||
3466 | for (i = 0; i < segs.length; i++) { | ||
3467 | seg = segs[i]; | ||
3468 | |||
3469 | if (isBgEvent(seg.event)) { | ||
3470 | bgSegs.push(seg); | ||
3471 | } | ||
3472 | else { | ||
3473 | fgSegs.push(seg); | ||
3474 | } | ||
3475 | } | ||
3476 | |||
3477 | // Render each different type of segment. | ||
3478 | // Each function may return a subset of the segs, segs that were actually rendered. | ||
3479 | bgSegs = this.renderBgSegs(bgSegs) || bgSegs; | ||
3480 | fgSegs = this.renderFgSegs(fgSegs) || fgSegs; | ||
3481 | |||
3482 | this.segs = bgSegs.concat(fgSegs); | ||
3483 | }, | ||
3484 | |||
3485 | |||
3486 | // Unrenders all events currently rendered on the grid | ||
3487 | destroyEvents: function() { | ||
3488 | this.triggerSegMouseout(); // trigger an eventMouseout if user's mouse is over an event | ||
3489 | |||
3490 | this.destroyFgSegs(); | ||
3491 | this.destroyBgSegs(); | ||
3492 | |||
3493 | this.segs = null; | ||
3494 | }, | ||
3495 | |||
3496 | |||
3497 | // Retrieves all rendered segment objects currently rendered on the grid | ||
3498 | getEventSegs: function() { | ||
3499 | return this.segs || []; | ||
3500 | }, | ||
3501 | |||
3502 | |||
3503 | /* Foreground Segment Rendering | ||
3504 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3505 | |||
3506 | |||
3507 | // Renders foreground event segments onto the grid. May return a subset of segs that were rendered. | ||
3508 | renderFgSegs: function(segs) { | ||
3509 | // subclasses must implement | ||
3510 | }, | ||
3511 | |||
3512 | |||
3513 | // Unrenders all currently rendered foreground segments | ||
3514 | destroyFgSegs: function() { | ||
3515 | // subclasses must implement | ||
3516 | }, | ||
3517 | |||
3518 | |||
3519 | // Renders and assigns an `el` property for each foreground event segment. | ||
3520 | // Only returns segments that successfully rendered. | ||
3521 | // A utility that subclasses may use. | ||
3522 | renderFgSegEls: function(segs, disableResizing) { | ||
3523 | var view = this.view; | ||
3524 | var html = ''; | ||
3525 | var renderedSegs = []; | ||
3526 | var i; | ||
3527 | |||
3528 | if (segs.length) { // don't build an empty html string | ||
3529 | |||
3530 | // build a large concatenation of event segment HTML | ||
3531 | for (i = 0; i < segs.length; i++) { | ||
3532 | html += this.fgSegHtml(segs[i], disableResizing); | ||
3533 | } | ||
3534 | |||
3535 | // Grab individual elements from the combined HTML string. Use each as the default rendering. | ||
3536 | // Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false. | ||
3537 | $(html).each(function(i, node) { | ||
3538 | var seg = segs[i]; | ||
3539 | var el = view.resolveEventEl(seg.event, $(node)); | ||
3540 | |||
3541 | if (el) { | ||
3542 | el.data('fc-seg', seg); // used by handlers | ||
3543 | seg.el = el; | ||
3544 | renderedSegs.push(seg); | ||
3545 | } | ||
3546 | }); | ||
3547 | } | ||
3548 | |||
3549 | return renderedSegs; | ||
3550 | }, | ||
3551 | |||
3552 | |||
3553 | // Generates the HTML for the default rendering of a foreground event segment. Used by renderFgSegEls() | ||
3554 | fgSegHtml: function(seg, disableResizing) { | ||
3555 | // subclasses should implement | ||
3556 | }, | ||
3557 | |||
3558 | |||
3559 | /* Background Segment Rendering | ||
3560 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3561 | |||
3562 | |||
3563 | // Renders the given background event segments onto the grid. | ||
3564 | // Returns a subset of the segs that were actually rendered. | ||
3565 | renderBgSegs: function(segs) { | ||
3566 | return this.renderFill('bgEvent', segs); | ||
3567 | }, | ||
3568 | |||
3569 | |||
3570 | // Unrenders all the currently rendered background event segments | ||
3571 | destroyBgSegs: function() { | ||
3572 | this.destroyFill('bgEvent'); | ||
3573 | }, | ||
3574 | |||
3575 | |||
3576 | // Renders a background event element, given the default rendering. Called by the fill system. | ||
3577 | bgEventSegEl: function(seg, el) { | ||
3578 | return this.view.resolveEventEl(seg.event, el); // will filter through eventRender | ||
3579 | }, | ||
3580 | |||
3581 | |||
3582 | // Generates an array of classNames to be used for the default rendering of a background event. | ||
3583 | // Called by the fill system. | ||
3584 | bgEventSegClasses: function(seg) { | ||
3585 | var event = seg.event; | ||
3586 | var source = event.source || {}; | ||
3587 | |||
3588 | return [ 'fc-bgevent' ].concat( | ||
3589 | event.className, | ||
3590 | source.className || [] | ||
3591 | ); | ||
3592 | }, | ||
3593 | |||
3594 | |||
3595 | // Generates a semicolon-separated CSS string to be used for the default rendering of a background event. | ||
3596 | // Called by the fill system. | ||
3597 | // TODO: consolidate with getEventSkinCss? | ||
3598 | bgEventSegCss: function(seg) { | ||
3599 | var view = this.view; | ||
3600 | var event = seg.event; | ||
3601 | var source = event.source || {}; | ||
3602 | |||
3603 | return { | ||
3604 | 'background-color': | ||
3605 | event.backgroundColor || | ||
3606 | event.color || | ||
3607 | source.backgroundColor || | ||
3608 | source.color || | ||
3609 | view.opt('eventBackgroundColor') || | ||
3610 | view.opt('eventColor') | ||
3611 | }; | ||
3612 | }, | ||
3613 | |||
3614 | |||
3615 | // Generates an array of classNames to be used for the rendering business hours overlay. Called by the fill system. | ||
3616 | businessHoursSegClasses: function(seg) { | ||
3617 | return [ 'fc-nonbusiness', 'fc-bgevent' ]; | ||
3618 | }, | ||
3619 | |||
3620 | |||
3621 | /* Handlers | ||
3622 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3623 | |||
3624 | |||
3625 | // Attaches event-element-related handlers to the container element and leverage bubbling | ||
3626 | bindSegHandlers: function() { | ||
3627 | var _this = this; | ||
3628 | var view = this.view; | ||
3629 | |||
3630 | $.each( | ||
3631 | { | ||
3632 | mouseenter: function(seg, ev) { | ||
3633 | _this.triggerSegMouseover(seg, ev); | ||
3634 | }, | ||
3635 | mouseleave: function(seg, ev) { | ||
3636 | _this.triggerSegMouseout(seg, ev); | ||
3637 | }, | ||
3638 | click: function(seg, ev) { | ||
3639 | return view.trigger('eventClick', this, seg.event, ev); // can return `false` to cancel | ||
3640 | }, | ||
3641 | mousedown: function(seg, ev) { | ||
3642 | if ($(ev.target).is('.fc-resizer') && view.isEventResizable(seg.event)) { | ||
3643 | _this.segResizeMousedown(seg, ev, $(ev.target).is('.fc-start-resizer')); | ||
3644 | } | ||
3645 | else if (view.isEventDraggable(seg.event)) { | ||
3646 | _this.segDragMousedown(seg, ev); | ||
3647 | } | ||
3648 | } | ||
3649 | }, | ||
3650 | function(name, func) { | ||
3651 | // attach the handler to the container element and only listen for real event elements via bubbling | ||
3652 | _this.el.on(name, '.fc-event-container > *', function(ev) { | ||
3653 | var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents | ||
3654 | |||
3655 | // only call the handlers if there is not a drag/resize in progress | ||
3656 | if (seg && !_this.isDraggingSeg && !_this.isResizingSeg) { | ||
3657 | return func.call(this, seg, ev); // `this` will be the event element | ||
3658 | } | ||
3659 | }); | ||
3660 | } | ||
3661 | ); | ||
3662 | }, | ||
3663 | |||
3664 | |||
3665 | // Updates internal state and triggers handlers for when an event element is moused over | ||
3666 | triggerSegMouseover: function(seg, ev) { | ||
3667 | if (!this.mousedOverSeg) { | ||
3668 | this.mousedOverSeg = seg; | ||
3669 | this.view.trigger('eventMouseover', seg.el[0], seg.event, ev); | ||
3670 | } | ||
3671 | }, | ||
3672 | |||
3673 | |||
3674 | // Updates internal state and triggers handlers for when an event element is moused out. | ||
3675 | // Can be given no arguments, in which case it will mouseout the segment that was previously moused over. | ||
3676 | triggerSegMouseout: function(seg, ev) { | ||
3677 | ev = ev || {}; // if given no args, make a mock mouse event | ||
3678 | |||
3679 | if (this.mousedOverSeg) { | ||
3680 | seg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment | ||
3681 | this.mousedOverSeg = null; | ||
3682 | this.view.trigger('eventMouseout', seg.el[0], seg.event, ev); | ||
3683 | } | ||
3684 | }, | ||
3685 | |||
3686 | |||
3687 | /* Event Dragging | ||
3688 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3689 | |||
3690 | |||
3691 | // Called when the user does a mousedown on an event, which might lead to dragging. | ||
3692 | // Generic enough to work with any type of Grid. | ||
3693 | segDragMousedown: function(seg, ev) { | ||
3694 | var _this = this; | ||
3695 | var view = this.view; | ||
3696 | var calendar = view.calendar; | ||
3697 | var el = seg.el; | ||
3698 | var event = seg.event; | ||
3699 | var dropLocation; | ||
3700 | |||
3701 | // A clone of the original element that will move with the mouse | ||
3702 | var mouseFollower = new MouseFollower(seg.el, { | ||
3703 | parentEl: view.el, | ||
3704 | opacity: view.opt('dragOpacity'), | ||
3705 | revertDuration: view.opt('dragRevertDuration'), | ||
3706 | zIndex: 2 // one above the .fc-view | ||
3707 | }); | ||
3708 | |||
3709 | // Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents | ||
3710 | // of the view. | ||
3711 | var dragListener = new CellDragListener(view.coordMap, { | ||
3712 | distance: 5, | ||
3713 | scroll: view.opt('dragScroll'), | ||
3714 | subjectEl: el, | ||
3715 | subjectCenter: true, | ||
3716 | listenStart: function(ev) { | ||
3717 | mouseFollower.hide(); // don't show until we know this is a real drag | ||
3718 | mouseFollower.start(ev); | ||
3719 | }, | ||
3720 | dragStart: function(ev) { | ||
3721 | _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported | ||
3722 | _this.segDragStart(seg, ev); | ||
3723 | view.hideEvent(event); // hide all event segments. our mouseFollower will take over | ||
3724 | }, | ||
3725 | cellOver: function(cell, isOrig, origCell) { | ||
3726 | |||
3727 | // starting cell could be forced (DayGrid.limit) | ||
3728 | if (seg.cell) { | ||
3729 | origCell = seg.cell; | ||
3730 | } | ||
3731 | |||
3732 | dropLocation = _this.computeEventDrop(origCell, cell, event); | ||
3733 | |||
3734 | if (dropLocation && !calendar.isEventRangeAllowed(dropLocation, event)) { | ||
3735 | disableCursor(); | ||
3736 | dropLocation = null; | ||
3737 | } | ||
3738 | |||
3739 | // if a valid drop location, have the subclass render a visual indication | ||
3740 | if (dropLocation && view.renderDrag(dropLocation, seg)) { | ||
3741 | mouseFollower.hide(); // if the subclass is already using a mock event "helper", hide our own | ||
3742 | } | ||
3743 | else { | ||
3744 | mouseFollower.show(); // otherwise, have the helper follow the mouse (no snapping) | ||
3745 | } | ||
3746 | |||
3747 | if (isOrig) { | ||
3748 | dropLocation = null; // needs to have moved cells to be a valid drop | ||
3749 | } | ||
3750 | }, | ||
3751 | cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells | ||
3752 | view.destroyDrag(); // unrender whatever was done in renderDrag | ||
3753 | mouseFollower.show(); // show in case we are moving out of all cells | ||
3754 | dropLocation = null; | ||
3755 | }, | ||
3756 | cellDone: function() { // Called after a cellOut OR before a dragStop | ||
3757 | enableCursor(); | ||
3758 | }, | ||
3759 | dragStop: function(ev) { | ||
3760 | // do revert animation if hasn't changed. calls a callback when finished (whether animation or not) | ||
3761 | mouseFollower.stop(!dropLocation, function() { | ||
3762 | view.destroyDrag(); | ||
3763 | view.showEvent(event); | ||
3764 | _this.segDragStop(seg, ev); | ||
3765 | |||
3766 | if (dropLocation) { | ||
3767 | view.reportEventDrop(event, dropLocation, this.largeUnit, el, ev); | ||
3768 | } | ||
3769 | }); | ||
3770 | }, | ||
3771 | listenStop: function() { | ||
3772 | mouseFollower.stop(); // put in listenStop in case there was a mousedown but the drag never started | ||
3773 | } | ||
3774 | }); | ||
3775 | |||
3776 | dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart | ||
3777 | }, | ||
3778 | |||
3779 | |||
3780 | // Called before event segment dragging starts | ||
3781 | segDragStart: function(seg, ev) { | ||
3782 | this.isDraggingSeg = true; | ||
3783 | this.view.trigger('eventDragStart', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy | ||
3784 | }, | ||
3785 | |||
3786 | |||
3787 | // Called after event segment dragging stops | ||
3788 | segDragStop: function(seg, ev) { | ||
3789 | this.isDraggingSeg = false; | ||
3790 | this.view.trigger('eventDragStop', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy | ||
3791 | }, | ||
3792 | |||
3793 | |||
3794 | // Given the cell an event drag began, and the cell event was dropped, calculates the new start/end/allDay | ||
3795 | // values for the event. Subclasses may override and set additional properties to be used by renderDrag. | ||
3796 | // A falsy returned value indicates an invalid drop. | ||
3797 | computeEventDrop: function(startCell, endCell, event) { | ||
3798 | var calendar = this.view.calendar; | ||
3799 | var dragStart = startCell.start; | ||
3800 | var dragEnd = endCell.start; | ||
3801 | var delta; | ||
3802 | var dropLocation; | ||
3803 | |||
3804 | if (dragStart.hasTime() === dragEnd.hasTime()) { | ||
3805 | delta = this.diffDates(dragEnd, dragStart); | ||
3806 | |||
3807 | // if an all-day event was in a timed area and it was dragged to a different time, | ||
3808 | // guarantee an end and adjust start/end to have times | ||
3809 | if (event.allDay && durationHasTime(delta)) { | ||
3810 | dropLocation = { | ||
3811 | start: event.start.clone(), | ||
3812 | end: calendar.getEventEnd(event), // will be an ambig day | ||
3813 | allDay: false // for normalizeEventRangeTimes | ||
3814 | }; | ||
3815 | calendar.normalizeEventRangeTimes(dropLocation); | ||
3816 | } | ||
3817 | // othewise, work off existing values | ||
3818 | else { | ||
3819 | dropLocation = { | ||
3820 | start: event.start.clone(), | ||
3821 | end: event.end ? event.end.clone() : null, | ||
3822 | allDay: event.allDay // keep it the same | ||
3823 | }; | ||
3824 | } | ||
3825 | |||
3826 | dropLocation.start.add(delta); | ||
3827 | if (dropLocation.end) { | ||
3828 | dropLocation.end.add(delta); | ||
3829 | } | ||
3830 | } | ||
3831 | else { | ||
3832 | // if switching from day <-> timed, start should be reset to the dropped date, and the end cleared | ||
3833 | dropLocation = { | ||
3834 | start: dragEnd.clone(), | ||
3835 | end: null, // end should be cleared | ||
3836 | allDay: !dragEnd.hasTime() | ||
3837 | }; | ||
3838 | } | ||
3839 | |||
3840 | return dropLocation; | ||
3841 | }, | ||
3842 | |||
3843 | |||
3844 | // Utility for apply dragOpacity to a jQuery set | ||
3845 | applyDragOpacity: function(els) { | ||
3846 | var opacity = this.view.opt('dragOpacity'); | ||
3847 | |||
3848 | if (opacity != null) { | ||
3849 | els.each(function(i, node) { | ||
3850 | // Don't use jQuery (will set an IE filter), do it the old fashioned way. | ||
3851 | // In IE8, a helper element will disappears if there's a filter. | ||
3852 | node.style.opacity = opacity; | ||
3853 | }); | ||
3854 | } | ||
3855 | }, | ||
3856 | |||
3857 | |||
3858 | /* External Element Dragging | ||
3859 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3860 | |||
3861 | |||
3862 | // Called when a jQuery UI drag is initiated anywhere in the DOM | ||
3863 | externalDragStart: function(ev, ui) { | ||
3864 | var view = this.view; | ||
3865 | var el; | ||
3866 | var accept; | ||
3867 | |||
3868 | if (view.opt('droppable')) { // only listen if this setting is on | ||
3869 | el = $((ui ? ui.item : null) || ev.target); | ||
3870 | |||
3871 | // Test that the dragged element passes the dropAccept selector or filter function. | ||
3872 | // FYI, the default is "*" (matches all) | ||
3873 | accept = view.opt('dropAccept'); | ||
3874 | if ($.isFunction(accept) ? accept.call(el[0], el) : el.is(accept)) { | ||
3875 | if (!this.isDraggingExternal) { // prevent double-listening if fired twice | ||
3876 | this.listenToExternalDrag(el, ev, ui); | ||
3877 | } | ||
3878 | } | ||
3879 | } | ||
3880 | }, | ||
3881 | |||
3882 | |||
3883 | // Called when a jQuery UI drag starts and it needs to be monitored for cell dropping | ||
3884 | listenToExternalDrag: function(el, ev, ui) { | ||
3885 | var _this = this; | ||
3886 | var meta = getDraggedElMeta(el); // extra data about event drop, including possible event to create | ||
3887 | var dragListener; | ||
3888 | var dropLocation; // a null value signals an unsuccessful drag | ||
3889 | |||
3890 | // listener that tracks mouse movement over date-associated pixel regions | ||
3891 | dragListener = new CellDragListener(this.coordMap, { | ||
3892 | listenStart: function() { | ||
3893 | _this.isDraggingExternal = true; | ||
3894 | }, | ||
3895 | cellOver: function(cell) { | ||
3896 | dropLocation = _this.computeExternalDrop(cell, meta); | ||
3897 | if (dropLocation) { | ||
3898 | _this.renderDrag(dropLocation); // called without a seg parameter | ||
3899 | } | ||
3900 | else { // invalid drop cell | ||
3901 | disableCursor(); | ||
3902 | } | ||
3903 | }, | ||
3904 | cellOut: function() { | ||
3905 | dropLocation = null; // signal unsuccessful | ||
3906 | _this.destroyDrag(); | ||
3907 | enableCursor(); | ||
3908 | }, | ||
3909 | dragStop: function() { | ||
3910 | _this.destroyDrag(); | ||
3911 | enableCursor(); | ||
3912 | |||
3913 | if (dropLocation) { // element was dropped on a valid date/time cell | ||
3914 | _this.view.reportExternalDrop(meta, dropLocation, el, ev, ui); | ||
3915 | } | ||
3916 | }, | ||
3917 | listenStop: function() { | ||
3918 | _this.isDraggingExternal = false; | ||
3919 | } | ||
3920 | }); | ||
3921 | |||
3922 | dragListener.startDrag(ev); // start listening immediately | ||
3923 | }, | ||
3924 | |||
3925 | |||
3926 | // Given a cell to be dropped upon, and misc data associated with the jqui drag (guaranteed to be a plain object), | ||
3927 | // returns start/end dates for the event that would result from the hypothetical drop. end might be null. | ||
3928 | // Returning a null value signals an invalid drop cell. | ||
3929 | computeExternalDrop: function(cell, meta) { | ||
3930 | var dropLocation = { | ||
3931 | start: cell.start.clone(), | ||
3932 | end: null | ||
3933 | }; | ||
3934 | |||
3935 | // if dropped on an all-day cell, and element's metadata specified a time, set it | ||
3936 | if (meta.startTime && !dropLocation.start.hasTime()) { | ||
3937 | dropLocation.start.time(meta.startTime); | ||
3938 | } | ||
3939 | |||
3940 | if (meta.duration) { | ||
3941 | dropLocation.end = dropLocation.start.clone().add(meta.duration); | ||
3942 | } | ||
3943 | |||
3944 | if (!this.view.calendar.isExternalDropRangeAllowed(dropLocation, meta.eventProps)) { | ||
3945 | return null; | ||
3946 | } | ||
3947 | |||
3948 | return dropLocation; | ||
3949 | }, | ||
3950 | |||
3951 | |||
3952 | |||
3953 | /* Drag Rendering (for both events and an external elements) | ||
3954 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3955 | |||
3956 | |||
3957 | // Renders a visual indication of an event or external element being dragged. | ||
3958 | // `dropLocation` contains hypothetical start/end/allDay values the event would have if dropped. end can be null. | ||
3959 | // `seg` is the internal segment object that is being dragged. If dragging an external element, `seg` is null. | ||
3960 | // A truthy returned value indicates this method has rendered a helper element. | ||
3961 | renderDrag: function(dropLocation, seg) { | ||
3962 | // subclasses must implement | ||
3963 | }, | ||
3964 | |||
3965 | |||
3966 | // Unrenders a visual indication of an event or external element being dragged | ||
3967 | destroyDrag: function() { | ||
3968 | // subclasses must implement | ||
3969 | }, | ||
3970 | |||
3971 | |||
3972 | /* Resizing | ||
3973 | ------------------------------------------------------------------------------------------------------------------*/ | ||
3974 | |||
3975 | |||
3976 | // Called when the user does a mousedown on an event's resizer, which might lead to resizing. | ||
3977 | // Generic enough to work with any type of Grid. | ||
3978 | segResizeMousedown: function(seg, ev, isStart) { | ||
3979 | var _this = this; | ||
3980 | var view = this.view; | ||
3981 | var calendar = view.calendar; | ||
3982 | var el = seg.el; | ||
3983 | var event = seg.event; | ||
3984 | var eventEnd = calendar.getEventEnd(event); | ||
3985 | var dragListener; | ||
3986 | var resizeLocation; // falsy if invalid resize | ||
3987 | |||
3988 | // Tracks mouse movement over the *grid's* coordinate map | ||
3989 | dragListener = new CellDragListener(this.coordMap, { | ||
3990 | distance: 5, | ||
3991 | scroll: view.opt('dragScroll'), | ||
3992 | subjectEl: el, | ||
3993 | dragStart: function(ev) { | ||
3994 | _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported | ||
3995 | _this.segResizeStart(seg, ev); | ||
3996 | }, | ||
3997 | cellOver: function(cell, isOrig, origCell) { | ||
3998 | resizeLocation = isStart ? | ||
3999 | _this.computeEventStartResize(origCell, cell, event) : | ||
4000 | _this.computeEventEndResize(origCell, cell, event); | ||
4001 | |||
4002 | if (resizeLocation) { | ||
4003 | if (!calendar.isEventRangeAllowed(resizeLocation, event)) { | ||
4004 | disableCursor(); | ||
4005 | resizeLocation = null; | ||
4006 | } | ||
4007 | // no change? (TODO: how does this work with timezones?) | ||
4008 | else if (resizeLocation.start.isSame(event.start) && resizeLocation.end.isSame(eventEnd)) { | ||
4009 | resizeLocation = null; | ||
4010 | } | ||
4011 | } | ||
4012 | |||
4013 | if (resizeLocation) { | ||
4014 | view.hideEvent(event); | ||
4015 | _this.renderEventResize(resizeLocation, seg); | ||
4016 | } | ||
4017 | }, | ||
4018 | cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells | ||
4019 | resizeLocation = null; | ||
4020 | }, | ||
4021 | cellDone: function() { // resets the rendering to show the original event | ||
4022 | _this.destroyEventResize(); | ||
4023 | view.showEvent(event); | ||
4024 | enableCursor(); | ||
4025 | }, | ||
4026 | dragStop: function(ev) { | ||
4027 | _this.segResizeStop(seg, ev); | ||
4028 | |||
4029 | if (resizeLocation) { // valid date to resize to? | ||
4030 | view.reportEventResize(event, resizeLocation, this.largeUnit, el, ev); | ||
4031 | } | ||
4032 | } | ||
4033 | }); | ||
4034 | |||
4035 | dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart | ||
4036 | }, | ||
4037 | |||
4038 | |||
4039 | // Called before event segment resizing starts | ||
4040 | segResizeStart: function(seg, ev) { | ||
4041 | this.isResizingSeg = true; | ||
4042 | this.view.trigger('eventResizeStart', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy | ||
4043 | }, | ||
4044 | |||
4045 | |||
4046 | // Called after event segment resizing stops | ||
4047 | segResizeStop: function(seg, ev) { | ||
4048 | this.isResizingSeg = false; | ||
4049 | this.view.trigger('eventResizeStop', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy | ||
4050 | }, | ||
4051 | |||
4052 | |||
4053 | // Returns new date-information for an event segment being resized from its start | ||
4054 | computeEventStartResize: function(startCell, endCell, event) { | ||
4055 | return this.computeEventResize('start', startCell, endCell, event); | ||
4056 | }, | ||
4057 | |||
4058 | |||
4059 | // Returns new date-information for an event segment being resized from its end | ||
4060 | computeEventEndResize: function(startCell, endCell, event) { | ||
4061 | return this.computeEventResize('end', startCell, endCell, event); | ||
4062 | }, | ||
4063 | |||
4064 | |||
4065 | // Returns new date-information for an event segment being resized from its start OR end | ||
4066 | // `type` is either 'start' or 'end' | ||
4067 | computeEventResize: function(type, startCell, endCell, event) { | ||
4068 | var calendar = this.view.calendar; | ||
4069 | var delta = this.diffDates(endCell[type], startCell[type]); | ||
4070 | var range; | ||
4071 | var defaultDuration; | ||
4072 | |||
4073 | // build original values to work from, guaranteeing a start and end | ||
4074 | range = { | ||
4075 | start: event.start.clone(), | ||
4076 | end: calendar.getEventEnd(event), | ||
4077 | allDay: event.allDay | ||
4078 | }; | ||
4079 | |||
4080 | // if an all-day event was in a timed area and was resized to a time, adjust start/end to have times | ||
4081 | if (range.allDay && durationHasTime(delta)) { | ||
4082 | range.allDay = false; | ||
4083 | calendar.normalizeEventRangeTimes(range); | ||
4084 | } | ||
4085 | |||
4086 | range[type].add(delta); // apply delta to start or end | ||
4087 | |||
4088 | // if the event was compressed too small, find a new reasonable duration for it | ||
4089 | if (!range.start.isBefore(range.end)) { | ||
4090 | |||
4091 | defaultDuration = event.allDay ? | ||
4092 | calendar.defaultAllDayEventDuration : | ||
4093 | calendar.defaultTimedEventDuration; | ||
4094 | |||
4095 | // between the cell's duration and the event's default duration, use the smaller of the two. | ||
4096 | // example: if year-length slots, and compressed to one slot, we don't want the event to be a year long | ||
4097 | if (this.cellDuration && this.cellDuration < defaultDuration) { | ||
4098 | defaultDuration = this.cellDuration; | ||
4099 | } | ||
4100 | |||
4101 | if (type == 'start') { // resizing the start? | ||
4102 | range.start = range.end.clone().subtract(defaultDuration); | ||
4103 | } | ||
4104 | else { // resizing the end? | ||
4105 | range.end = range.start.clone().add(defaultDuration); | ||
4106 | } | ||
4107 | } | ||
4108 | |||
4109 | return range; | ||
4110 | }, | ||
4111 | |||
4112 | |||
4113 | // Renders a visual indication of an event being resized. | ||
4114 | // `range` has the updated dates of the event. `seg` is the original segment object involved in the drag. | ||
4115 | renderEventResize: function(range, seg) { | ||
4116 | // subclasses must implement | ||
4117 | }, | ||
4118 | |||
4119 | |||
4120 | // Unrenders a visual indication of an event being resized. | ||
4121 | destroyEventResize: function() { | ||
4122 | // subclasses must implement | ||
4123 | }, | ||
4124 | |||
4125 | |||
4126 | /* Rendering Utils | ||
4127 | ------------------------------------------------------------------------------------------------------------------*/ | ||
4128 | |||
4129 | |||
4130 | // Compute the text that should be displayed on an event's element. | ||
4131 | // `range` can be the Event object itself, or something range-like, with at least a `start`. | ||
4132 | // If event times are disabled, or the event has no time, will return a blank string. | ||
4133 | // If not specified, formatStr will default to the eventTimeFormat setting, | ||
4134 | // and displayEnd will default to the displayEventEnd setting. | ||
4135 | getEventTimeText: function(range, formatStr, displayEnd) { | ||
4136 | |||
4137 | if (formatStr == null) { | ||
4138 | formatStr = this.eventTimeFormat; | ||
4139 | } | ||
4140 | |||
4141 | if (displayEnd == null) { | ||
4142 | displayEnd = this.displayEventEnd; | ||
4143 | } | ||
4144 | |||
4145 | if (this.displayEventTime && range.start.hasTime()) { | ||
4146 | if (displayEnd && range.end) { | ||
4147 | return this.view.formatRange(range, formatStr); | ||
4148 | } | ||
4149 | else { | ||
4150 | return range.start.format(formatStr); | ||
4151 | } | ||
4152 | } | ||
4153 | |||
4154 | return ''; | ||
4155 | }, | ||
4156 | |||
4157 | |||
4158 | // Generic utility for generating the HTML classNames for an event segment's element | ||
4159 | getSegClasses: function(seg, isDraggable, isResizable) { | ||
4160 | var event = seg.event; | ||
4161 | var classes = [ | ||
4162 | 'fc-event', | ||
4163 | seg.isStart ? 'fc-start' : 'fc-not-start', | ||
4164 | seg.isEnd ? 'fc-end' : 'fc-not-end' | ||
4165 | ].concat( | ||
4166 | event.className, | ||
4167 | event.source ? event.source.className : [] | ||
4168 | ); | ||
4169 | |||
4170 | if (isDraggable) { | ||
4171 | classes.push('fc-draggable'); | ||
4172 | } | ||
4173 | if (isResizable) { | ||
4174 | classes.push('fc-resizable'); | ||
4175 | } | ||
4176 | |||
4177 | return classes; | ||
4178 | }, | ||
4179 | |||
4180 | |||
4181 | // Utility for generating event skin-related CSS properties | ||
4182 | getEventSkinCss: function(event) { | ||
4183 | var view = this.view; | ||
4184 | var source = event.source || {}; | ||
4185 | var eventColor = event.color; | ||
4186 | var sourceColor = source.color; | ||
4187 | var optionColor = view.opt('eventColor'); | ||
4188 | |||
4189 | return { | ||
4190 | 'background-color': | ||
4191 | event.backgroundColor || | ||
4192 | eventColor || | ||
4193 | source.backgroundColor || | ||
4194 | sourceColor || | ||
4195 | view.opt('eventBackgroundColor') || | ||
4196 | optionColor, | ||
4197 | 'border-color': | ||
4198 | event.borderColor || | ||
4199 | eventColor || | ||
4200 | source.borderColor || | ||
4201 | sourceColor || | ||
4202 | view.opt('eventBorderColor') || | ||
4203 | optionColor, | ||
4204 | color: | ||
4205 | event.textColor || | ||
4206 | source.textColor || | ||
4207 | view.opt('eventTextColor') | ||
4208 | }; | ||
4209 | }, | ||
4210 | |||
4211 | |||
4212 | /* Converting events -> ranges -> segs | ||
4213 | ------------------------------------------------------------------------------------------------------------------*/ | ||
4214 | |||
4215 | |||
4216 | // Converts an array of event objects into an array of event segment objects. | ||
4217 | // A custom `rangeToSegsFunc` may be given for arbitrarily slicing up events. | ||
4218 | // Doesn't guarantee an order for the resulting array. | ||
4219 | eventsToSegs: function(events, rangeToSegsFunc) { | ||
4220 | var eventRanges = this.eventsToRanges(events); | ||
4221 | var segs = []; | ||
4222 | var i; | ||
4223 | |||
4224 | for (i = 0; i < eventRanges.length; i++) { | ||
4225 | segs.push.apply( | ||
4226 | segs, | ||
4227 | this.eventRangeToSegs(eventRanges[i], rangeToSegsFunc) | ||
4228 | ); | ||
4229 | } | ||
4230 | |||
4231 | return segs; | ||
4232 | }, | ||
4233 | |||
4234 | |||
4235 | // Converts an array of events into an array of "range" objects. | ||
4236 | // A "range" object is a plain object with start/end properties denoting the time it covers. Also an event property. | ||
4237 | // For "normal" events, this will be identical to the event's start/end, but for "inverse-background" events, | ||
4238 | // will create an array of ranges that span the time *not* covered by the given event. | ||
4239 | // Doesn't guarantee an order for the resulting array. | ||
4240 | eventsToRanges: function(events) { | ||
4241 | var _this = this; | ||
4242 | var eventsById = groupEventsById(events); | ||
4243 | var ranges = []; | ||
4244 | |||
4245 | // group by ID so that related inverse-background events can be rendered together | ||
4246 | $.each(eventsById, function(id, eventGroup) { | ||
4247 | if (eventGroup.length) { | ||
4248 | ranges.push.apply( | ||
4249 | ranges, | ||
4250 | isInverseBgEvent(eventGroup[0]) ? | ||
4251 | _this.eventsToInverseRanges(eventGroup) : | ||
4252 | _this.eventsToNormalRanges(eventGroup) | ||
4253 | ); | ||
4254 | } | ||
4255 | }); | ||
4256 | |||
4257 | return ranges; | ||
4258 | }, | ||
4259 | |||
4260 | |||
4261 | // Converts an array of "normal" events (not inverted rendering) into a parallel array of ranges | ||
4262 | eventsToNormalRanges: function(events) { | ||
4263 | var calendar = this.view.calendar; | ||
4264 | var ranges = []; | ||
4265 | var i, event; | ||
4266 | var eventStart, eventEnd; | ||
4267 | |||
4268 | for (i = 0; i < events.length; i++) { | ||
4269 | event = events[i]; | ||
4270 | |||
4271 | // make copies and normalize by stripping timezone | ||
4272 | eventStart = event.start.clone().stripZone(); | ||
4273 | eventEnd = calendar.getEventEnd(event).stripZone(); | ||
4274 | |||
4275 | ranges.push({ | ||
4276 | event: event, | ||
4277 | start: eventStart, | ||
4278 | end: eventEnd, | ||
4279 | eventStartMS: +eventStart, | ||
4280 | eventDurationMS: eventEnd - eventStart | ||
4281 | }); | ||
4282 | } | ||
4283 | |||
4284 | return ranges; | ||
4285 | }, | ||
4286 | |||
4287 | |||
4288 | // Converts an array of events, with inverse-background rendering, into an array of range objects. | ||
4289 | // The range objects will cover all the time NOT covered by the events. | ||
4290 | eventsToInverseRanges: function(events) { | ||
4291 | var view = this.view; | ||
4292 | var viewStart = view.start.clone().stripZone(); // normalize timezone | ||
4293 | var viewEnd = view.end.clone().stripZone(); // normalize timezone | ||
4294 | var normalRanges = this.eventsToNormalRanges(events); // will give us normalized dates we can use w/o copies | ||
4295 | var inverseRanges = []; | ||
4296 | var event0 = events[0]; // assign this to each range's `.event` | ||
4297 | var start = viewStart; // the end of the previous range. the start of the new range | ||
4298 | var i, normalRange; | ||
4299 | |||
4300 | // ranges need to be in order. required for our date-walking algorithm | ||
4301 | normalRanges.sort(compareNormalRanges); | ||
4302 | |||
4303 | for (i = 0; i < normalRanges.length; i++) { | ||
4304 | normalRange = normalRanges[i]; | ||
4305 | |||
4306 | // add the span of time before the event (if there is any) | ||
4307 | if (normalRange.start > start) { // compare millisecond time (skip any ambig logic) | ||
4308 | inverseRanges.push({ | ||
4309 | event: event0, | ||
4310 | start: start, | ||
4311 | end: normalRange.start | ||
4312 | }); | ||
4313 | } | ||
4314 | |||
4315 | start = normalRange.end; | ||
4316 | } | ||
4317 | |||
4318 | // add the span of time after the last event (if there is any) | ||
4319 | if (start < viewEnd) { // compare millisecond time (skip any ambig logic) | ||
4320 | inverseRanges.push({ | ||
4321 | event: event0, | ||
4322 | start: start, | ||
4323 | end: viewEnd | ||
4324 | }); | ||
4325 | } | ||
4326 | |||
4327 | return inverseRanges; | ||
4328 | }, | ||
4329 | |||
4330 | |||
4331 | // Slices the given event range into one or more segment objects. | ||
4332 | // A `rangeToSegsFunc` custom slicing function can be given. | ||
4333 | eventRangeToSegs: function(eventRange, rangeToSegsFunc) { | ||
4334 | var segs; | ||
4335 | var i, seg; | ||
4336 | |||
4337 | if (rangeToSegsFunc) { | ||
4338 | segs = rangeToSegsFunc(eventRange); | ||
4339 | } | ||
4340 | else { | ||
4341 | segs = this.rangeToSegs(eventRange); // defined by the subclass | ||
4342 | } | ||
4343 | |||
4344 | for (i = 0; i < segs.length; i++) { | ||
4345 | seg = segs[i]; | ||
4346 | seg.event = eventRange.event; | ||
4347 | seg.eventStartMS = eventRange.eventStartMS; | ||
4348 | seg.eventDurationMS = eventRange.eventDurationMS; | ||
4349 | } | ||
4350 | |||
4351 | return segs; | ||
4352 | } | ||
4353 | |||
4354 | }); | ||
4355 | |||
4356 | |||
4357 | /* Utilities | ||
4358 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
4359 | |||
4360 | |||
4361 | function isBgEvent(event) { // returns true if background OR inverse-background | ||
4362 | var rendering = getEventRendering(event); | ||
4363 | return rendering === 'background' || rendering === 'inverse-background'; | ||
4364 | } | ||
4365 | |||
4366 | |||
4367 | function isInverseBgEvent(event) { | ||
4368 | return getEventRendering(event) === 'inverse-background'; | ||
4369 | } | ||
4370 | |||
4371 | |||
4372 | function getEventRendering(event) { | ||
4373 | return firstDefined((event.source || {}).rendering, event.rendering); | ||
4374 | } | ||
4375 | |||
4376 | |||
4377 | function groupEventsById(events) { | ||
4378 | var eventsById = {}; | ||
4379 | var i, event; | ||
4380 | |||
4381 | for (i = 0; i < events.length; i++) { | ||
4382 | event = events[i]; | ||
4383 | (eventsById[event._id] || (eventsById[event._id] = [])).push(event); | ||
4384 | } | ||
4385 | |||
4386 | return eventsById; | ||
4387 | } | ||
4388 | |||
4389 | |||
4390 | // A cmp function for determining which non-inverted "ranges" (see above) happen earlier | ||
4391 | function compareNormalRanges(range1, range2) { | ||
4392 | return range1.eventStartMS - range2.eventStartMS; // earlier ranges go first | ||
4393 | } | ||
4394 | |||
4395 | |||
4396 | // A cmp function for determining which segments should take visual priority | ||
4397 | // DOES NOT WORK ON INVERTED BACKGROUND EVENTS because they have no eventStartMS/eventDurationMS | ||
4398 | function compareSegs(seg1, seg2) { | ||
4399 | return seg1.eventStartMS - seg2.eventStartMS || // earlier events go first | ||
4400 | seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first | ||
4401 | seg2.event.allDay - seg1.event.allDay || // tie? put all-day events first (booleans cast to 0/1) | ||
4402 | (seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title | ||
4403 | } | ||
4404 | |||
4405 | fc.compareSegs = compareSegs; // export | ||
4406 | |||
4407 | |||
4408 | /* External-Dragging-Element Data | ||
4409 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
4410 | |||
4411 | // Require all HTML5 data-* attributes used by FullCalendar to have this prefix. | ||
4412 | // A value of '' will query attributes like data-event. A value of 'fc' will query attributes like data-fc-event. | ||
4413 | fc.dataAttrPrefix = ''; | ||
4414 | |||
4415 | // Given a jQuery element that might represent a dragged FullCalendar event, returns an intermediate data structure | ||
4416 | // to be used for Event Object creation. | ||
4417 | // A defined `.eventProps`, even when empty, indicates that an event should be created. | ||
4418 | function getDraggedElMeta(el) { | ||
4419 | var prefix = fc.dataAttrPrefix; | ||
4420 | var eventProps; // properties for creating the event, not related to date/time | ||
4421 | var startTime; // a Duration | ||
4422 | var duration; | ||
4423 | var stick; | ||
4424 | |||
4425 | if (prefix) { prefix += '-'; } | ||
4426 | eventProps = el.data(prefix + 'event') || null; | ||
4427 | |||
4428 | if (eventProps) { | ||
4429 | if (typeof eventProps === 'object') { | ||
4430 | eventProps = $.extend({}, eventProps); // make a copy | ||
4431 | } | ||
4432 | else { // something like 1 or true. still signal event creation | ||
4433 | eventProps = {}; | ||
4434 | } | ||
4435 | |||
4436 | // pluck special-cased date/time properties | ||
4437 | startTime = eventProps.start; | ||
4438 | if (startTime == null) { startTime = eventProps.time; } // accept 'time' as well | ||
4439 | duration = eventProps.duration; | ||
4440 | stick = eventProps.stick; | ||
4441 | delete eventProps.start; | ||
4442 | delete eventProps.time; | ||
4443 | delete eventProps.duration; | ||
4444 | delete eventProps.stick; | ||
4445 | } | ||
4446 | |||
4447 | // fallback to standalone attribute values for each of the date/time properties | ||
4448 | if (startTime == null) { startTime = el.data(prefix + 'start'); } | ||
4449 | if (startTime == null) { startTime = el.data(prefix + 'time'); } // accept 'time' as well | ||
4450 | if (duration == null) { duration = el.data(prefix + 'duration'); } | ||
4451 | if (stick == null) { stick = el.data(prefix + 'stick'); } | ||
4452 | |||
4453 | // massage into correct data types | ||
4454 | startTime = startTime != null ? moment.duration(startTime) : null; | ||
4455 | duration = duration != null ? moment.duration(duration) : null; | ||
4456 | stick = Boolean(stick); | ||
4457 | |||
4458 | return { eventProps: eventProps, startTime: startTime, duration: duration, stick: stick }; | ||
4459 | } | ||
4460 | |||
4461 | |||
4462 | ;; | ||
4463 | |||
4464 | /* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week. | ||
4465 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
4466 | |||
4467 | var DayGrid = Grid.extend({ | ||
4468 | |||
4469 | numbersVisible: false, // should render a row for day/week numbers? set by outside view. TODO: make internal | ||
4470 | bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid | ||
4471 | breakOnWeeks: null, // should create a new row for each week? set by outside view | ||
4472 | |||
4473 | cellDates: null, // flat chronological array of each cell's dates | ||
4474 | dayToCellOffsets: null, // maps days offsets from grid's start date, to cell offsets | ||
4475 | |||
4476 | rowEls: null, // set of fake row elements | ||
4477 | dayEls: null, // set of whole-day elements comprising the row's background | ||
4478 | helperEls: null, // set of cell skeleton elements for rendering the mock event "helper" | ||
4479 | |||
4480 | |||
4481 | constructor: function() { | ||
4482 | Grid.apply(this, arguments); | ||
4483 | |||
4484 | this.cellDuration = moment.duration(1, 'day'); // for Grid system | ||
4485 | }, | ||
4486 | |||
4487 | |||
4488 | // Renders the rows and columns into the component's `this.el`, which should already be assigned. | ||
4489 | // isRigid determins whether the individual rows should ignore the contents and be a constant height. | ||
4490 | // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient. | ||
4491 | renderDates: function(isRigid) { | ||
4492 | var view = this.view; | ||
4493 | var rowCnt = this.rowCnt; | ||
4494 | var colCnt = this.colCnt; | ||
4495 | var cellCnt = rowCnt * colCnt; | ||
4496 | var html = ''; | ||
4497 | var row; | ||
4498 | var i, cell; | ||
4499 | |||
4500 | for (row = 0; row < rowCnt; row++) { | ||
4501 | html += this.dayRowHtml(row, isRigid); | ||
4502 | } | ||
4503 | this.el.html(html); | ||
4504 | |||
4505 | this.rowEls = this.el.find('.fc-row'); | ||
4506 | this.dayEls = this.el.find('.fc-day'); | ||
4507 | |||
4508 | // trigger dayRender with each cell's element | ||
4509 | for (i = 0; i < cellCnt; i++) { | ||
4510 | cell = this.getCell(i); | ||
4511 | view.trigger('dayRender', null, cell.start, this.dayEls.eq(i)); | ||
4512 | } | ||
4513 | }, | ||
4514 | |||
4515 | |||
4516 | destroyDates: function() { | ||
4517 | this.destroySegPopover(); | ||
4518 | }, | ||
4519 | |||
4520 | |||
4521 | renderBusinessHours: function() { | ||
4522 | var events = this.view.calendar.getBusinessHoursEvents(true); // wholeDay=true | ||
4523 | var segs = this.eventsToSegs(events); | ||
4524 | |||
4525 | this.renderFill('businessHours', segs, 'bgevent'); | ||
4526 | }, | ||
4527 | |||
4528 | |||
4529 | // Generates the HTML for a single row. `row` is the row number. | ||
4530 | dayRowHtml: function(row, isRigid) { | ||
4531 | var view = this.view; | ||
4532 | var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ]; | ||
4533 | |||
4534 | if (isRigid) { | ||
4535 | classes.push('fc-rigid'); | ||
4536 | } | ||
4537 | |||
4538 | return '' + | ||
4539 | '<div class="' + classes.join(' ') + '">' + | ||
4540 | '<div class="fc-bg">' + | ||
4541 | '<table>' + | ||
4542 | this.rowHtml('day', row) + // leverages RowRenderer. calls dayCellHtml() | ||
4543 | '</table>' + | ||
4544 | '</div>' + | ||
4545 | '<div class="fc-content-skeleton">' + | ||
4546 | '<table>' + | ||
4547 | (this.numbersVisible ? | ||
4548 | '<thead>' + | ||
4549 | this.rowHtml('number', row) + // leverages RowRenderer. View will define render method | ||
4550 | '</thead>' : | ||
4551 | '' | ||
4552 | ) + | ||
4553 | '</table>' + | ||
4554 | '</div>' + | ||
4555 | '</div>'; | ||
4556 | }, | ||
4557 | |||
4558 | |||
4559 | // Renders the HTML for a whole-day cell. Will eventually end up in the day-row's background. | ||
4560 | // We go through a 'day' row type instead of just doing a 'bg' row type so that the View can do custom rendering | ||
4561 | // specifically for whole-day rows, whereas a 'bg' might also be used for other purposes (TimeGrid bg for example). | ||
4562 | dayCellHtml: function(cell) { | ||
4563 | return this.bgCellHtml(cell); | ||
4564 | }, | ||
4565 | |||
4566 | |||
4567 | /* Options | ||
4568 | ------------------------------------------------------------------------------------------------------------------*/ | ||
4569 | |||
4570 | |||
4571 | // Computes a default column header formatting string if `colFormat` is not explicitly defined | ||
4572 | computeColHeadFormat: function() { | ||
4573 | if (this.rowCnt > 1) { // more than one week row. day numbers will be in each cell | ||
4574 | return 'ddd'; // "Sat" | ||
4575 | } | ||
4576 | else if (this.colCnt > 1) { // multiple days, so full single date string WON'T be in title text | ||
4577 | return this.view.opt('dayOfMonthFormat'); // "Sat 12/10" | ||
4578 | } | ||
4579 | else { // single day, so full single date string will probably be in title text | ||
4580 | return 'dddd'; // "Saturday" | ||
4581 | } | ||
4582 | }, | ||
4583 | |||
4584 | |||
4585 | // Computes a default event time formatting string if `timeFormat` is not explicitly defined | ||
4586 | computeEventTimeFormat: function() { | ||
4587 | return this.view.opt('extraSmallTimeFormat'); // like "6p" or "6:30p" | ||
4588 | }, | ||
4589 | |||
4590 | |||
4591 | // Computes a default `displayEventEnd` value if one is not expliclty defined | ||
4592 | computeDisplayEventEnd: function() { | ||
4593 | return this.colCnt == 1; // we'll likely have space if there's only one day | ||
4594 | }, | ||
4595 | |||
4596 | |||
4597 | /* Cell System | ||
4598 | ------------------------------------------------------------------------------------------------------------------*/ | ||
4599 | |||
4600 | |||
4601 | // Initializes row/col information | ||
4602 | updateCells: function() { | ||
4603 | var cellDates; | ||
4604 | var firstDay; | ||
4605 | var rowCnt; | ||
4606 | var colCnt; | ||
4607 | |||
4608 | this.updateCellDates(); // populates cellDates and dayToCellOffsets | ||
4609 | cellDates = this.cellDates; | ||
4610 | |||
4611 | if (this.breakOnWeeks) { | ||
4612 | // count columns until the day-of-week repeats | ||
4613 | firstDay = cellDates[0].day(); | ||
4614 | for (colCnt = 1; colCnt < cellDates.length; colCnt++) { | ||
4615 | if (cellDates[colCnt].day() == firstDay) { | ||
4616 | break; | ||
4617 | } | ||
4618 | } | ||
4619 | rowCnt = Math.ceil(cellDates.length / colCnt); | ||
4620 | } | ||
4621 | else { | ||
4622 | rowCnt = 1; | ||
4623 | colCnt = cellDates.length; | ||
4624 | } | ||
4625 | |||
4626 | this.rowCnt = rowCnt; | ||
4627 | this.colCnt = colCnt; | ||
4628 | }, | ||
4629 | |||
4630 | |||
4631 | // Populates cellDates and dayToCellOffsets | ||
4632 | updateCellDates: function() { | ||
4633 | var view = this.view; | ||
4634 | var date = this.start.clone(); | ||
4635 | var dates = []; | ||
4636 | var offset = -1; | ||
4637 | var offsets = []; | ||
4638 | |||
4639 | while (date.isBefore(this.end)) { // loop each day from start to end | ||
4640 | if (view.isHiddenDay(date)) { | ||
4641 | offsets.push(offset + 0.5); // mark that it's between offsets | ||
4642 | } | ||
4643 | else { | ||
4644 | offset++; | ||
4645 | offsets.push(offset); | ||
4646 | dates.push(date.clone()); | ||
4647 | } | ||
4648 | date.add(1, 'days'); | ||
4649 | } | ||
4650 | |||
4651 | this.cellDates = dates; | ||
4652 | this.dayToCellOffsets = offsets; | ||
4653 | }, | ||
4654 | |||
4655 | |||
4656 | // Given a cell object, generates its start date. Returns a reference-free copy. | ||
4657 | computeCellDate: function(cell) { | ||
4658 | var colCnt = this.colCnt; | ||
4659 | var index = cell.row * colCnt + (this.isRTL ? colCnt - cell.col - 1 : cell.col); | ||
4660 | |||
4661 | return this.cellDates[index].clone(); | ||
4662 | }, | ||
4663 | |||
4664 | |||
4665 | // Retrieves the element representing the given row | ||
4666 | getRowEl: function(row) { | ||
4667 | return this.rowEls.eq(row); | ||
4668 | }, | ||
4669 | |||
4670 | |||
4671 | // Retrieves the element representing the given column | ||
4672 | getColEl: function(col) { | ||
4673 | return this.dayEls.eq(col); | ||
4674 | }, | ||
4675 | |||
4676 | |||
4677 | // Gets the whole-day element associated with the cell | ||
4678 | getCellDayEl: function(cell) { | ||
4679 | return this.dayEls.eq(cell.row * this.colCnt + cell.col); | ||
4680 | }, | ||
4681 | |||
4682 | |||
4683 | // Overrides Grid's method for when row coordinates are computed | ||
4684 | computeRowCoords: function() { | ||
4685 | var rowCoords = Grid.prototype.computeRowCoords.call(this); // call the super-method | ||
4686 | |||
4687 | // hack for extending last row (used by AgendaView) | ||
4688 | rowCoords[rowCoords.length - 1].bottom += this.bottomCoordPadding; | ||
4689 | |||
4690 | return rowCoords; | ||
4691 | }, | ||
4692 | |||
4693 | |||
4694 | /* Dates | ||
4695 | ------------------------------------------------------------------------------------------------------------------*/ | ||
4696 | |||
4697 | |||
4698 | // Slices up a date range by row into an array of segments | ||
4699 | rangeToSegs: function(range) { | ||
4700 | var isRTL = this.isRTL; | ||
4701 | var rowCnt = this.rowCnt; | ||
4702 | var colCnt = this.colCnt; | ||
4703 | var segs = []; | ||
4704 | var first, last; // inclusive cell-offset range for given range | ||
4705 | var row; | ||
4706 | var rowFirst, rowLast; // inclusive cell-offset range for current row | ||
4707 | var isStart, isEnd; | ||
4708 | var segFirst, segLast; // inclusive cell-offset range for segment | ||
4709 | var seg; | ||
4710 | |||
4711 | range = this.view.computeDayRange(range); // make whole-day range, considering nextDayThreshold | ||
4712 | first = this.dateToCellOffset(range.start); | ||
4713 | last = this.dateToCellOffset(range.end.subtract(1, 'days')); // offset of inclusive end date | ||
4714 | |||
4715 | for (row = 0; row < rowCnt; row++) { | ||
4716 | rowFirst = row * colCnt; | ||
4717 | rowLast = rowFirst + colCnt - 1; | ||
4718 | |||
4719 | // intersect segment's offset range with the row's | ||
4720 | segFirst = Math.max(rowFirst, first); | ||
4721 | segLast = Math.min(rowLast, last); | ||
4722 | |||
4723 | // deal with in-between indices | ||
4724 | segFirst = Math.ceil(segFirst); // in-between starts round to next cell | ||
4725 | segLast = Math.floor(segLast); // in-between ends round to prev cell | ||
4726 | |||
4727 | if (segFirst <= segLast) { // was there any intersection with the current row? | ||
4728 | |||
4729 | // must be matching integers to be the segment's start/end | ||
4730 | isStart = segFirst === first; | ||
4731 | isEnd = segLast === last; | ||
4732 | |||
4733 | // translate offsets to be relative to start-of-row | ||
4734 | segFirst -= rowFirst; | ||
4735 | segLast -= rowFirst; | ||
4736 | |||
4737 | seg = { row: row, isStart: isStart, isEnd: isEnd }; | ||
4738 | if (isRTL) { | ||
4739 | seg.leftCol = colCnt - segLast - 1; | ||
4740 | seg.rightCol = colCnt - segFirst - 1; | ||
4741 | } | ||
4742 | else { | ||
4743 | seg.leftCol = segFirst; | ||
4744 | seg.rightCol = segLast; | ||
4745 | } | ||
4746 | segs.push(seg); | ||
4747 | } | ||
4748 | } | ||
4749 | |||
4750 | return segs; | ||
4751 | }, | ||
4752 | |||
4753 | |||
4754 | // Given a date, returns its chronolocial cell-offset from the first cell of the grid. | ||
4755 | // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets. | ||
4756 | // If before the first offset, returns a negative number. | ||
4757 | // If after the last offset, returns an offset past the last cell offset. | ||
4758 | // Only works for *start* dates of cells. Will not work for exclusive end dates for cells. | ||
4759 | dateToCellOffset: function(date) { | ||
4760 | var offsets = this.dayToCellOffsets; | ||
4761 | var day = date.diff(this.start, 'days'); | ||
4762 | |||
4763 | if (day < 0) { | ||
4764 | return offsets[0] - 1; | ||
4765 | } | ||
4766 | else if (day >= offsets.length) { | ||
4767 | return offsets[offsets.length - 1] + 1; | ||
4768 | } | ||
4769 | else { | ||
4770 | return offsets[day]; | ||
4771 | } | ||
4772 | }, | ||
4773 | |||
4774 | |||
4775 | /* Event Drag Visualization | ||
4776 | ------------------------------------------------------------------------------------------------------------------*/ | ||
4777 | // TODO: move to DayGrid.event, similar to what we did with Grid's drag methods | ||
4778 | |||
4779 | |||
4780 | // Renders a visual indication of an event or external element being dragged. | ||
4781 | // The dropLocation's end can be null. seg can be null. See Grid::renderDrag for more info. | ||
4782 | renderDrag: function(dropLocation, seg) { | ||
4783 | |||
4784 | // always render a highlight underneath | ||
4785 | this.renderHighlight( | ||
4786 | this.view.calendar.ensureVisibleEventRange(dropLocation) // needs to be a proper range | ||
4787 | ); | ||
4788 | |||
4789 | // if a segment from the same calendar but another component is being dragged, render a helper event | ||
4790 | if (seg && !seg.el.closest(this.el).length) { | ||
4791 | |||
4792 | this.renderRangeHelper(dropLocation, seg); | ||
4793 | this.applyDragOpacity(this.helperEls); | ||
4794 | |||
4795 | return true; // a helper has been rendered | ||
4796 | } | ||
4797 | }, | ||
4798 | |||
4799 | |||
4800 | // Unrenders any visual indication of a hovering event | ||
4801 | destroyDrag: function() { | ||
4802 | this.destroyHighlight(); | ||
4803 | this.destroyHelper(); | ||
4804 | }, | ||
4805 | |||
4806 | |||
4807 | /* Event Resize Visualization | ||
4808 | ------------------------------------------------------------------------------------------------------------------*/ | ||
4809 | |||
4810 | |||
4811 | // Renders a visual indication of an event being resized | ||
4812 | renderEventResize: function(range, seg) { | ||
4813 | this.renderHighlight(range); | ||
4814 | this.renderRangeHelper(range, seg); | ||
4815 | }, | ||
4816 | |||
4817 | |||
4818 | // Unrenders a visual indication of an event being resized | ||
4819 | destroyEventResize: function() { | ||
4820 | this.destroyHighlight(); | ||
4821 | this.destroyHelper(); | ||
4822 | }, | ||
4823 | |||
4824 | |||
4825 | /* Event Helper | ||
4826 | ------------------------------------------------------------------------------------------------------------------*/ | ||
4827 | |||
4828 | |||
4829 | // Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null. | ||
4830 | renderHelper: function(event, sourceSeg) { | ||
4831 | var helperNodes = []; | ||
4832 | var segs = this.eventsToSegs([ event ]); | ||
4833 | var rowStructs; | ||
4834 | |||
4835 | segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered | ||
4836 | rowStructs = this.renderSegRows(segs); | ||
4837 | |||
4838 | // inject each new event skeleton into each associated row | ||
4839 | this.rowEls.each(function(row, rowNode) { | ||
4840 | var rowEl = $(rowNode); // the .fc-row | ||
4841 | var skeletonEl = $('<div class="fc-helper-skeleton"><table/></div>'); // will be absolutely positioned | ||
4842 | var skeletonTop; | ||
4843 | |||
4844 | // If there is an original segment, match the top position. Otherwise, put it at the row's top level | ||
4845 | if (sourceSeg && sourceSeg.row === row) { | ||
4846 | skeletonTop = sourceSeg.el.position().top; | ||
4847 | } | ||
4848 | else { | ||
4849 | skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top; | ||
4850 | } | ||
4851 | |||
4852 | skeletonEl.css('top', skeletonTop) | ||
4853 | .find('table') | ||
4854 | .append(rowStructs[row].tbodyEl); | ||
4855 | |||
4856 | rowEl.append(skeletonEl); | ||
4857 | helperNodes.push(skeletonEl[0]); | ||
4858 | }); | ||
4859 | |||
4860 | this.helperEls = $(helperNodes); // array -> jQuery set | ||
4861 | }, | ||
4862 | |||
4863 | |||
4864 | // Unrenders any visual indication of a mock helper event | ||
4865 | destroyHelper: function() { | ||
4866 | if (this.helperEls) { | ||
4867 | this.helperEls.remove(); | ||
4868 | this.helperEls = null; | ||
4869 | } | ||
4870 | }, | ||
4871 | |||
4872 | |||
4873 | /* Fill System (highlight, background events, business hours) | ||
4874 | ------------------------------------------------------------------------------------------------------------------*/ | ||
4875 | |||
4876 | |||
4877 | fillSegTag: 'td', // override the default tag name | ||
4878 | |||
4879 | |||
4880 | // Renders a set of rectangles over the given segments of days. | ||
4881 | // Only returns segments that successfully rendered. | ||
4882 | renderFill: function(type, segs, className) { | ||
4883 | var nodes = []; | ||
4884 | var i, seg; | ||
4885 | var skeletonEl; | ||
4886 | |||
4887 | segs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs | ||
4888 | |||
4889 | for (i = 0; i < segs.length; i++) { | ||
4890 | seg = segs[i]; | ||
4891 | skeletonEl = this.renderFillRow(type, seg, className); | ||
4892 | this.rowEls.eq(seg.row).append(skeletonEl); | ||
4893 | nodes.push(skeletonEl[0]); | ||
4894 | } | ||
4895 | |||
4896 | this.elsByFill[type] = $(nodes); | ||
4897 | |||
4898 | return segs; | ||
4899 | }, | ||
4900 | |||
4901 | |||
4902 | // Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered. | ||
4903 | renderFillRow: function(type, seg, className) { | ||
4904 | var colCnt = this.colCnt; | ||
4905 | var startCol = seg.leftCol; | ||
4906 | var endCol = seg.rightCol + 1; | ||
4907 | var skeletonEl; | ||
4908 | var trEl; | ||
4909 | |||
4910 | className = className || type.toLowerCase(); | ||
4911 | |||
4912 | skeletonEl = $( | ||
4913 | '<div class="fc-' + className + '-skeleton">' + | ||
4914 | '<table><tr/></table>' + | ||
4915 | '</div>' | ||
4916 | ); | ||
4917 | trEl = skeletonEl.find('tr'); | ||
4918 | |||
4919 | if (startCol > 0) { | ||
4920 | trEl.append('<td colspan="' + startCol + '"/>'); | ||
4921 | } | ||
4922 | |||
4923 | trEl.append( | ||
4924 | seg.el.attr('colspan', endCol - startCol) | ||
4925 | ); | ||
4926 | |||
4927 | if (endCol < colCnt) { | ||
4928 | trEl.append('<td colspan="' + (colCnt - endCol) + '"/>'); | ||
4929 | } | ||
4930 | |||
4931 | this.bookendCells(trEl, type); | ||
4932 | |||
4933 | return skeletonEl; | ||
4934 | } | ||
4935 | |||
4936 | }); | ||
4937 | |||
4938 | ;; | ||
4939 | |||
4940 | /* Event-rendering methods for the DayGrid class | ||
4941 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
4942 | |||
4943 | DayGrid.mixin({ | ||
4944 | |||
4945 | rowStructs: null, // an array of objects, each holding information about a row's foreground event-rendering | ||
4946 | |||
4947 | |||
4948 | // Unrenders all events currently rendered on the grid | ||
4949 | destroyEvents: function() { | ||
4950 | this.destroySegPopover(); // removes the "more.." events popover | ||
4951 | Grid.prototype.destroyEvents.apply(this, arguments); // calls the super-method | ||
4952 | }, | ||
4953 | |||
4954 | |||
4955 | // Retrieves all rendered segment objects currently rendered on the grid | ||
4956 | getEventSegs: function() { | ||
4957 | return Grid.prototype.getEventSegs.call(this) // get the segments from the super-method | ||
4958 | .concat(this.popoverSegs || []); // append the segments from the "more..." popover | ||
4959 | }, | ||
4960 | |||
4961 | |||
4962 | // Renders the given background event segments onto the grid | ||
4963 | renderBgSegs: function(segs) { | ||
4964 | |||
4965 | // don't render timed background events | ||
4966 | var allDaySegs = $.grep(segs, function(seg) { | ||
4967 | return seg.event.allDay; | ||
4968 | }); | ||
4969 | |||
4970 | return Grid.prototype.renderBgSegs.call(this, allDaySegs); // call the super-method | ||
4971 | }, | ||
4972 | |||
4973 | |||
4974 | // Renders the given foreground event segments onto the grid | ||
4975 | renderFgSegs: function(segs) { | ||
4976 | var rowStructs; | ||
4977 | |||
4978 | // render an `.el` on each seg | ||
4979 | // returns a subset of the segs. segs that were actually rendered | ||
4980 | segs = this.renderFgSegEls(segs); | ||
4981 | |||
4982 | rowStructs = this.rowStructs = this.renderSegRows(segs); | ||
4983 | |||
4984 | // append to each row's content skeleton | ||
4985 | this.rowEls.each(function(i, rowNode) { | ||
4986 | $(rowNode).find('.fc-content-skeleton > table').append( | ||
4987 | rowStructs[i].tbodyEl | ||
4988 | ); | ||
4989 | }); | ||
4990 | |||
4991 | return segs; // return only the segs that were actually rendered | ||
4992 | }, | ||
4993 | |||
4994 | |||
4995 | // Unrenders all currently rendered foreground event segments | ||
4996 | destroyFgSegs: function() { | ||
4997 | var rowStructs = this.rowStructs || []; | ||
4998 | var rowStruct; | ||
4999 | |||
5000 | while ((rowStruct = rowStructs.pop())) { | ||
5001 | rowStruct.tbodyEl.remove(); | ||
5002 | } | ||
5003 | |||
5004 | this.rowStructs = null; | ||
5005 | }, | ||
5006 | |||
5007 | |||
5008 | // Uses the given events array to generate <tbody> elements that should be appended to each row's content skeleton. | ||
5009 | // Returns an array of rowStruct objects (see the bottom of `renderSegRow`). | ||
5010 | // PRECONDITION: each segment shoud already have a rendered and assigned `.el` | ||
5011 | renderSegRows: function(segs) { | ||
5012 | var rowStructs = []; | ||
5013 | var segRows; | ||
5014 | var row; | ||
5015 | |||
5016 | segRows = this.groupSegRows(segs); // group into nested arrays | ||
5017 | |||
5018 | // iterate each row of segment groupings | ||
5019 | for (row = 0; row < segRows.length; row++) { | ||
5020 | rowStructs.push( | ||
5021 | this.renderSegRow(row, segRows[row]) | ||
5022 | ); | ||
5023 | } | ||
5024 | |||
5025 | return rowStructs; | ||
5026 | }, | ||
5027 | |||
5028 | |||
5029 | // Builds the HTML to be used for the default element for an individual segment | ||
5030 | fgSegHtml: function(seg, disableResizing) { | ||
5031 | var view = this.view; | ||
5032 | var event = seg.event; | ||
5033 | var isDraggable = view.isEventDraggable(event); | ||
5034 | var isResizableFromStart = !disableResizing && event.allDay && | ||
5035 | seg.isStart && view.isEventResizableFromStart(event); | ||
5036 | var isResizableFromEnd = !disableResizing && event.allDay && | ||
5037 | seg.isEnd && view.isEventResizableFromEnd(event); | ||
5038 | var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd); | ||
5039 | var skinCss = cssToStr(this.getEventSkinCss(event)); | ||
5040 | var timeHtml = ''; | ||
5041 | var timeText; | ||
5042 | var titleHtml; | ||
5043 | |||
5044 | classes.unshift('fc-day-grid-event', 'fc-h-event'); | ||
5045 | |||
5046 | // Only display a timed events time if it is the starting segment | ||
5047 | if (seg.isStart) { | ||
5048 | timeText = this.getEventTimeText(event); | ||
5049 | if (timeText) { | ||
5050 | timeHtml = '<span class="fc-time">' + htmlEscape(timeText) + '</span>'; | ||
5051 | } | ||
5052 | } | ||
5053 | |||
5054 | titleHtml = | ||
5055 | '<span class="fc-title">' + | ||
5056 | (htmlEscape(event.title || '') || ' ') + // we always want one line of height | ||
5057 | '</span>'; | ||
5058 | |||
5059 | return '<a class="' + classes.join(' ') + '"' + | ||
5060 | (event.url ? | ||
5061 | ' href="' + htmlEscape(event.url) + '"' : | ||
5062 | '' | ||
5063 | ) + | ||
5064 | (skinCss ? | ||
5065 | ' style="' + skinCss + '"' : | ||
5066 | '' | ||
5067 | ) + | ||
5068 | '>' + | ||
5069 | '<div class="fc-content">' + | ||
5070 | (this.isRTL ? | ||
5071 | titleHtml + ' ' + timeHtml : // put a natural space in between | ||
5072 | timeHtml + ' ' + titleHtml // | ||
5073 | ) + | ||
5074 | '</div>' + | ||
5075 | (isResizableFromStart ? | ||
5076 | '<div class="fc-resizer fc-start-resizer" />' : | ||
5077 | '' | ||
5078 | ) + | ||
5079 | (isResizableFromEnd ? | ||
5080 | '<div class="fc-resizer fc-end-resizer" />' : | ||
5081 | '' | ||
5082 | ) + | ||
5083 | '</a>'; | ||
5084 | }, | ||
5085 | |||
5086 | |||
5087 | // Given a row # and an array of segments all in the same row, render a <tbody> element, a skeleton that contains | ||
5088 | // the segments. Returns object with a bunch of internal data about how the render was calculated. | ||
5089 | // NOTE: modifies rowSegs | ||
5090 | renderSegRow: function(row, rowSegs) { | ||
5091 | var colCnt = this.colCnt; | ||
5092 | var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels | ||
5093 | var levelCnt = Math.max(1, segLevels.length); // ensure at least one level | ||
5094 | var tbody = $('<tbody/>'); | ||
5095 | var segMatrix = []; // lookup for which segments are rendered into which level+col cells | ||
5096 | var cellMatrix = []; // lookup for all <td> elements of the level+col matrix | ||
5097 | var loneCellMatrix = []; // lookup for <td> elements that only take up a single column | ||
5098 | var i, levelSegs; | ||
5099 | var col; | ||
5100 | var tr; | ||
5101 | var j, seg; | ||
5102 | var td; | ||
5103 | |||
5104 | // populates empty cells from the current column (`col`) to `endCol` | ||
5105 | function emptyCellsUntil(endCol) { | ||
5106 | while (col < endCol) { | ||
5107 | // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell | ||
5108 | td = (loneCellMatrix[i - 1] || [])[col]; | ||
5109 | if (td) { | ||
5110 | td.attr( | ||
5111 | 'rowspan', | ||
5112 | parseInt(td.attr('rowspan') || 1, 10) + 1 | ||
5113 | ); | ||
5114 | } | ||
5115 | else { | ||
5116 | td = $('<td/>'); | ||
5117 | tr.append(td); | ||
5118 | } | ||
5119 | cellMatrix[i][col] = td; | ||
5120 | loneCellMatrix[i][col] = td; | ||
5121 | col++; | ||
5122 | } | ||
5123 | } | ||
5124 | |||
5125 | for (i = 0; i < levelCnt; i++) { // iterate through all levels | ||
5126 | levelSegs = segLevels[i]; | ||
5127 | col = 0; | ||
5128 | tr = $('<tr/>'); | ||
5129 | |||
5130 | segMatrix.push([]); | ||
5131 | cellMatrix.push([]); | ||
5132 | loneCellMatrix.push([]); | ||
5133 | |||
5134 | // levelCnt might be 1 even though there are no actual levels. protect against this. | ||
5135 | // this single empty row is useful for styling. | ||
5136 | if (levelSegs) { | ||
5137 | for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level | ||
5138 | seg = levelSegs[j]; | ||
5139 | |||
5140 | emptyCellsUntil(seg.leftCol); | ||
5141 | |||
5142 | // create a container that occupies or more columns. append the event element. | ||
5143 | td = $('<td class="fc-event-container"/>').append(seg.el); | ||
5144 | if (seg.leftCol != seg.rightCol) { | ||
5145 | td.attr('colspan', seg.rightCol - seg.leftCol + 1); | ||
5146 | } | ||
5147 | else { // a single-column segment | ||
5148 | loneCellMatrix[i][col] = td; | ||
5149 | } | ||
5150 | |||
5151 | while (col <= seg.rightCol) { | ||
5152 | cellMatrix[i][col] = td; | ||
5153 | segMatrix[i][col] = seg; | ||
5154 | col++; | ||
5155 | } | ||
5156 | |||
5157 | tr.append(td); | ||
5158 | } | ||
5159 | } | ||
5160 | |||
5161 | emptyCellsUntil(colCnt); // finish off the row | ||
5162 | this.bookendCells(tr, 'eventSkeleton'); | ||
5163 | tbody.append(tr); | ||
5164 | } | ||
5165 | |||
5166 | return { // a "rowStruct" | ||
5167 | row: row, // the row number | ||
5168 | tbodyEl: tbody, | ||
5169 | cellMatrix: cellMatrix, | ||
5170 | segMatrix: segMatrix, | ||
5171 | segLevels: segLevels, | ||
5172 | segs: rowSegs | ||
5173 | }; | ||
5174 | }, | ||
5175 | |||
5176 | |||
5177 | // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels. | ||
5178 | // NOTE: modifies segs | ||
5179 | buildSegLevels: function(segs) { | ||
5180 | var levels = []; | ||
5181 | var i, seg; | ||
5182 | var j; | ||
5183 | |||
5184 | // Give preference to elements with certain criteria, so they have | ||
5185 | // a chance to be closer to the top. | ||
5186 | segs.sort(compareSegs); | ||
5187 | |||
5188 | for (i = 0; i < segs.length; i++) { | ||
5189 | seg = segs[i]; | ||
5190 | |||
5191 | // loop through levels, starting with the topmost, until the segment doesn't collide with other segments | ||
5192 | for (j = 0; j < levels.length; j++) { | ||
5193 | if (!isDaySegCollision(seg, levels[j])) { | ||
5194 | break; | ||
5195 | } | ||
5196 | } | ||
5197 | // `j` now holds the desired subrow index | ||
5198 | seg.level = j; | ||
5199 | |||
5200 | // create new level array if needed and append segment | ||
5201 | (levels[j] || (levels[j] = [])).push(seg); | ||
5202 | } | ||
5203 | |||
5204 | // order segments left-to-right. very important if calendar is RTL | ||
5205 | for (j = 0; j < levels.length; j++) { | ||
5206 | levels[j].sort(compareDaySegCols); | ||
5207 | } | ||
5208 | |||
5209 | return levels; | ||
5210 | }, | ||
5211 | |||
5212 | |||
5213 | // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row | ||
5214 | groupSegRows: function(segs) { | ||
5215 | var segRows = []; | ||
5216 | var i; | ||
5217 | |||
5218 | for (i = 0; i < this.rowCnt; i++) { | ||
5219 | segRows.push([]); | ||
5220 | } | ||
5221 | |||
5222 | for (i = 0; i < segs.length; i++) { | ||
5223 | segRows[segs[i].row].push(segs[i]); | ||
5224 | } | ||
5225 | |||
5226 | return segRows; | ||
5227 | } | ||
5228 | |||
5229 | }); | ||
5230 | |||
5231 | |||
5232 | // Computes whether two segments' columns collide. They are assumed to be in the same row. | ||
5233 | function isDaySegCollision(seg, otherSegs) { | ||
5234 | var i, otherSeg; | ||
5235 | |||
5236 | for (i = 0; i < otherSegs.length; i++) { | ||
5237 | otherSeg = otherSegs[i]; | ||
5238 | |||
5239 | if ( | ||
5240 | otherSeg.leftCol <= seg.rightCol && | ||
5241 | otherSeg.rightCol >= seg.leftCol | ||
5242 | ) { | ||
5243 | return true; | ||
5244 | } | ||
5245 | } | ||
5246 | |||
5247 | return false; | ||
5248 | } | ||
5249 | |||
5250 | |||
5251 | // A cmp function for determining the leftmost event | ||
5252 | function compareDaySegCols(a, b) { | ||
5253 | return a.leftCol - b.leftCol; | ||
5254 | } | ||
5255 | |||
5256 | ;; | ||
5257 | |||
5258 | /* Methods relate to limiting the number events for a given day on a DayGrid | ||
5259 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
5260 | // NOTE: all the segs being passed around in here are foreground segs | ||
5261 | |||
5262 | DayGrid.mixin({ | ||
5263 | |||
5264 | segPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible | ||
5265 | popoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible | ||
5266 | |||
5267 | |||
5268 | destroySegPopover: function() { | ||
5269 | if (this.segPopover) { | ||
5270 | this.segPopover.hide(); // will trigger destruction of `segPopover` and `popoverSegs` | ||
5271 | } | ||
5272 | }, | ||
5273 | |||
5274 | |||
5275 | // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid. | ||
5276 | // `levelLimit` can be false (don't limit), a number, or true (should be computed). | ||
5277 | limitRows: function(levelLimit) { | ||
5278 | var rowStructs = this.rowStructs || []; | ||
5279 | var row; // row # | ||
5280 | var rowLevelLimit; | ||
5281 | |||
5282 | for (row = 0; row < rowStructs.length; row++) { | ||
5283 | this.unlimitRow(row); | ||
5284 | |||
5285 | if (!levelLimit) { | ||
5286 | rowLevelLimit = false; | ||
5287 | } | ||
5288 | else if (typeof levelLimit === 'number') { | ||
5289 | rowLevelLimit = levelLimit; | ||
5290 | } | ||
5291 | else { | ||
5292 | rowLevelLimit = this.computeRowLevelLimit(row); | ||
5293 | } | ||
5294 | |||
5295 | if (rowLevelLimit !== false) { | ||
5296 | this.limitRow(row, rowLevelLimit); | ||
5297 | } | ||
5298 | } | ||
5299 | }, | ||
5300 | |||
5301 | |||
5302 | // Computes the number of levels a row will accomodate without going outside its bounds. | ||
5303 | // Assumes the row is "rigid" (maintains a constant height regardless of what is inside). | ||
5304 | // `row` is the row number. | ||
5305 | computeRowLevelLimit: function(row) { | ||
5306 | var rowEl = this.rowEls.eq(row); // the containing "fake" row div | ||
5307 | var rowHeight = rowEl.height(); // TODO: cache somehow? | ||
5308 | var trEls = this.rowStructs[row].tbodyEl.children(); | ||
5309 | var i, trEl; | ||
5310 | var trHeight; | ||
5311 | |||
5312 | function iterInnerHeights(i, childNode) { | ||
5313 | trHeight = Math.max(trHeight, $(childNode).outerHeight()); | ||
5314 | } | ||
5315 | |||
5316 | // Reveal one level <tr> at a time and stop when we find one out of bounds | ||
5317 | for (i = 0; i < trEls.length; i++) { | ||
5318 | trEl = trEls.eq(i).removeClass('fc-limited'); // reset to original state (reveal) | ||
5319 | |||
5320 | // with rowspans>1 and IE8, trEl.outerHeight() would return the height of the largest cell, | ||
5321 | // so instead, find the tallest inner content element. | ||
5322 | trHeight = 0; | ||
5323 | trEl.find('> td > :first-child').each(iterInnerHeights); | ||
5324 | |||
5325 | if (trEl.position().top + trHeight > rowHeight) { | ||
5326 | return i; | ||
5327 | } | ||
5328 | } | ||
5329 | |||
5330 | return false; // should not limit at all | ||
5331 | }, | ||
5332 | |||
5333 | |||
5334 | // Limits the given grid row to the maximum number of levels and injects "more" links if necessary. | ||
5335 | // `row` is the row number. | ||
5336 | // `levelLimit` is a number for the maximum (inclusive) number of levels allowed. | ||
5337 | limitRow: function(row, levelLimit) { | ||
5338 | var _this = this; | ||
5339 | var rowStruct = this.rowStructs[row]; | ||
5340 | var moreNodes = []; // array of "more" <a> links and <td> DOM nodes | ||
5341 | var col = 0; // col #, left-to-right (not chronologically) | ||
5342 | var cell; | ||
5343 | var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right | ||
5344 | var cellMatrix; // a matrix (by level, then column) of all <td> jQuery elements in the row | ||
5345 | var limitedNodes; // array of temporarily hidden level <tr> and segment <td> DOM nodes | ||
5346 | var i, seg; | ||
5347 | var segsBelow; // array of segment objects below `seg` in the current `col` | ||
5348 | var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies | ||
5349 | var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column) | ||
5350 | var td, rowspan; | ||
5351 | var segMoreNodes; // array of "more" <td> cells that will stand-in for the current seg's cell | ||
5352 | var j; | ||
5353 | var moreTd, moreWrap, moreLink; | ||
5354 | |||
5355 | // Iterates through empty level cells and places "more" links inside if need be | ||
5356 | function emptyCellsUntil(endCol) { // goes from current `col` to `endCol` | ||
5357 | while (col < endCol) { | ||
5358 | cell = _this.getCell(row, col); | ||
5359 | segsBelow = _this.getCellSegs(cell, levelLimit); | ||
5360 | if (segsBelow.length) { | ||
5361 | td = cellMatrix[levelLimit - 1][col]; | ||
5362 | moreLink = _this.renderMoreLink(cell, segsBelow); | ||
5363 | moreWrap = $('<div/>').append(moreLink); | ||
5364 | td.append(moreWrap); | ||
5365 | moreNodes.push(moreWrap[0]); | ||
5366 | } | ||
5367 | col++; | ||
5368 | } | ||
5369 | } | ||
5370 | |||
5371 | if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit? | ||
5372 | levelSegs = rowStruct.segLevels[levelLimit - 1]; | ||
5373 | cellMatrix = rowStruct.cellMatrix; | ||
5374 | |||
5375 | limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level <tr> elements past the limit | ||
5376 | .addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array | ||
5377 | |||
5378 | // iterate though segments in the last allowable level | ||
5379 | for (i = 0; i < levelSegs.length; i++) { | ||
5380 | seg = levelSegs[i]; | ||
5381 | emptyCellsUntil(seg.leftCol); // process empty cells before the segment | ||
5382 | |||
5383 | // determine *all* segments below `seg` that occupy the same columns | ||
5384 | colSegsBelow = []; | ||
5385 | totalSegsBelow = 0; | ||
5386 | while (col <= seg.rightCol) { | ||
5387 | cell = this.getCell(row, col); | ||
5388 | segsBelow = this.getCellSegs(cell, levelLimit); | ||
5389 | colSegsBelow.push(segsBelow); | ||
5390 | totalSegsBelow += segsBelow.length; | ||
5391 | col++; | ||
5392 | } | ||
5393 | |||
5394 | if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links? | ||
5395 | td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell | ||
5396 | rowspan = td.attr('rowspan') || 1; | ||
5397 | segMoreNodes = []; | ||
5398 | |||
5399 | // make a replacement <td> for each column the segment occupies. will be one for each colspan | ||
5400 | for (j = 0; j < colSegsBelow.length; j++) { | ||
5401 | moreTd = $('<td class="fc-more-cell"/>').attr('rowspan', rowspan); | ||
5402 | segsBelow = colSegsBelow[j]; | ||
5403 | cell = this.getCell(row, seg.leftCol + j); | ||
5404 | moreLink = this.renderMoreLink(cell, [ seg ].concat(segsBelow)); // count seg as hidden too | ||
5405 | moreWrap = $('<div/>').append(moreLink); | ||
5406 | moreTd.append(moreWrap); | ||
5407 | segMoreNodes.push(moreTd[0]); | ||
5408 | moreNodes.push(moreTd[0]); | ||
5409 | } | ||
5410 | |||
5411 | td.addClass('fc-limited').after($(segMoreNodes)); // hide original <td> and inject replacements | ||
5412 | limitedNodes.push(td[0]); | ||
5413 | } | ||
5414 | } | ||
5415 | |||
5416 | emptyCellsUntil(this.colCnt); // finish off the level | ||
5417 | rowStruct.moreEls = $(moreNodes); // for easy undoing later | ||
5418 | rowStruct.limitedEls = $(limitedNodes); // for easy undoing later | ||
5419 | } | ||
5420 | }, | ||
5421 | |||
5422 | |||
5423 | // Reveals all levels and removes all "more"-related elements for a grid's row. | ||
5424 | // `row` is a row number. | ||
5425 | unlimitRow: function(row) { | ||
5426 | var rowStruct = this.rowStructs[row]; | ||
5427 | |||
5428 | if (rowStruct.moreEls) { | ||
5429 | rowStruct.moreEls.remove(); | ||
5430 | rowStruct.moreEls = null; | ||
5431 | } | ||
5432 | |||
5433 | if (rowStruct.limitedEls) { | ||
5434 | rowStruct.limitedEls.removeClass('fc-limited'); | ||
5435 | rowStruct.limitedEls = null; | ||
5436 | } | ||
5437 | }, | ||
5438 | |||
5439 | |||
5440 | // Renders an <a> element that represents hidden event element for a cell. | ||
5441 | // Responsible for attaching click handler as well. | ||
5442 | renderMoreLink: function(cell, hiddenSegs) { | ||
5443 | var _this = this; | ||
5444 | var view = this.view; | ||
5445 | |||
5446 | return $('<a class="fc-more"/>') | ||
5447 | .text( | ||
5448 | this.getMoreLinkText(hiddenSegs.length) | ||
5449 | ) | ||
5450 | .on('click', function(ev) { | ||
5451 | var clickOption = view.opt('eventLimitClick'); | ||
5452 | var date = cell.start; | ||
5453 | var moreEl = $(this); | ||
5454 | var dayEl = _this.getCellDayEl(cell); | ||
5455 | var allSegs = _this.getCellSegs(cell); | ||
5456 | |||
5457 | // rescope the segments to be within the cell's date | ||
5458 | var reslicedAllSegs = _this.resliceDaySegs(allSegs, date); | ||
5459 | var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date); | ||
5460 | |||
5461 | if (typeof clickOption === 'function') { | ||
5462 | // the returned value can be an atomic option | ||
5463 | clickOption = view.trigger('eventLimitClick', null, { | ||
5464 | date: date, | ||
5465 | dayEl: dayEl, | ||
5466 | moreEl: moreEl, | ||
5467 | segs: reslicedAllSegs, | ||
5468 | hiddenSegs: reslicedHiddenSegs | ||
5469 | }, ev); | ||
5470 | } | ||
5471 | |||
5472 | if (clickOption === 'popover') { | ||
5473 | _this.showSegPopover(cell, moreEl, reslicedAllSegs); | ||
5474 | } | ||
5475 | else if (typeof clickOption === 'string') { // a view name | ||
5476 | view.calendar.zoomTo(date, clickOption); | ||
5477 | } | ||
5478 | }); | ||
5479 | }, | ||
5480 | |||
5481 | |||
5482 | // Reveals the popover that displays all events within a cell | ||
5483 | showSegPopover: function(cell, moreLink, segs) { | ||
5484 | var _this = this; | ||
5485 | var view = this.view; | ||
5486 | var moreWrap = moreLink.parent(); // the <div> wrapper around the <a> | ||
5487 | var topEl; // the element we want to match the top coordinate of | ||
5488 | var options; | ||
5489 | |||
5490 | if (this.rowCnt == 1) { | ||
5491 | topEl = view.el; // will cause the popover to cover any sort of header | ||
5492 | } | ||
5493 | else { | ||
5494 | topEl = this.rowEls.eq(cell.row); // will align with top of row | ||
5495 | } | ||
5496 | |||
5497 | options = { | ||
5498 | className: 'fc-more-popover', | ||
5499 | content: this.renderSegPopoverContent(cell, segs), | ||
5500 | parentEl: this.el, | ||
5501 | top: topEl.offset().top, | ||
5502 | autoHide: true, // when the user clicks elsewhere, hide the popover | ||
5503 | viewportConstrain: view.opt('popoverViewportConstrain'), | ||
5504 | hide: function() { | ||
5505 | // destroy everything when the popover is hidden | ||
5506 | _this.segPopover.destroy(); | ||
5507 | _this.segPopover = null; | ||
5508 | _this.popoverSegs = null; | ||
5509 | } | ||
5510 | }; | ||
5511 | |||
5512 | // Determine horizontal coordinate. | ||
5513 | // We use the moreWrap instead of the <td> to avoid border confusion. | ||
5514 | if (this.isRTL) { | ||
5515 | options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border | ||
5516 | } | ||
5517 | else { | ||
5518 | options.left = moreWrap.offset().left - 1; // -1 to be over cell border | ||
5519 | } | ||
5520 | |||
5521 | this.segPopover = new Popover(options); | ||
5522 | this.segPopover.show(); | ||
5523 | }, | ||
5524 | |||
5525 | |||
5526 | // Builds the inner DOM contents of the segment popover | ||
5527 | renderSegPopoverContent: function(cell, segs) { | ||
5528 | var view = this.view; | ||
5529 | var isTheme = view.opt('theme'); | ||
5530 | var title = cell.start.format(view.opt('dayPopoverFormat')); | ||
5531 | var content = $( | ||
5532 | '<div class="fc-header ' + view.widgetHeaderClass + '">' + | ||
5533 | '<span class="fc-close ' + | ||
5534 | (isTheme ? 'ui-icon ui-icon-closethick' : 'fc-icon fc-icon-x') + | ||
5535 | '"></span>' + | ||
5536 | '<span class="fc-title">' + | ||
5537 | htmlEscape(title) + | ||
5538 | '</span>' + | ||
5539 | '<div class="fc-clear"/>' + | ||
5540 | '</div>' + | ||
5541 | '<div class="fc-body ' + view.widgetContentClass + '">' + | ||
5542 | '<div class="fc-event-container"></div>' + | ||
5543 | '</div>' | ||
5544 | ); | ||
5545 | var segContainer = content.find('.fc-event-container'); | ||
5546 | var i; | ||
5547 | |||
5548 | // render each seg's `el` and only return the visible segs | ||
5549 | segs = this.renderFgSegEls(segs, true); // disableResizing=true | ||
5550 | this.popoverSegs = segs; | ||
5551 | |||
5552 | for (i = 0; i < segs.length; i++) { | ||
5553 | |||
5554 | // because segments in the popover are not part of a grid coordinate system, provide a hint to any | ||
5555 | // grids that want to do drag-n-drop about which cell it came from | ||
5556 | segs[i].cell = cell; | ||
5557 | |||
5558 | segContainer.append(segs[i].el); | ||
5559 | } | ||
5560 | |||
5561 | return content; | ||
5562 | }, | ||
5563 | |||
5564 | |||
5565 | // Given the events within an array of segment objects, reslice them to be in a single day | ||
5566 | resliceDaySegs: function(segs, dayDate) { | ||
5567 | |||
5568 | // build an array of the original events | ||
5569 | var events = $.map(segs, function(seg) { | ||
5570 | return seg.event; | ||
5571 | }); | ||
5572 | |||
5573 | var dayStart = dayDate.clone().stripTime(); | ||
5574 | var dayEnd = dayStart.clone().add(1, 'days'); | ||
5575 | var dayRange = { start: dayStart, end: dayEnd }; | ||
5576 | |||
5577 | // slice the events with a custom slicing function | ||
5578 | segs = this.eventsToSegs( | ||
5579 | events, | ||
5580 | function(range) { | ||
5581 | var seg = intersectionToSeg(range, dayRange); // undefind if no intersection | ||
5582 | return seg ? [ seg ] : []; // must return an array of segments | ||
5583 | } | ||
5584 | ); | ||
5585 | |||
5586 | // force an order because eventsToSegs doesn't guarantee one | ||
5587 | segs.sort(compareSegs); | ||
5588 | |||
5589 | return segs; | ||
5590 | }, | ||
5591 | |||
5592 | |||
5593 | // Generates the text that should be inside a "more" link, given the number of events it represents | ||
5594 | getMoreLinkText: function(num) { | ||
5595 | var opt = this.view.opt('eventLimitText'); | ||
5596 | |||
5597 | if (typeof opt === 'function') { | ||
5598 | return opt(num); | ||
5599 | } | ||
5600 | else { | ||
5601 | return '+' + num + ' ' + opt; | ||
5602 | } | ||
5603 | }, | ||
5604 | |||
5605 | |||
5606 | // Returns segments within a given cell. | ||
5607 | // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs. | ||
5608 | getCellSegs: function(cell, startLevel) { | ||
5609 | var segMatrix = this.rowStructs[cell.row].segMatrix; | ||
5610 | var level = startLevel || 0; | ||
5611 | var segs = []; | ||
5612 | var seg; | ||
5613 | |||
5614 | while (level < segMatrix.length) { | ||
5615 | seg = segMatrix[level][cell.col]; | ||
5616 | if (seg) { | ||
5617 | segs.push(seg); | ||
5618 | } | ||
5619 | level++; | ||
5620 | } | ||
5621 | |||
5622 | return segs; | ||
5623 | } | ||
5624 | |||
5625 | }); | ||
5626 | |||
5627 | ;; | ||
5628 | |||
5629 | /* A component that renders one or more columns of vertical time slots | ||
5630 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
5631 | |||
5632 | var TimeGrid = Grid.extend({ | ||
5633 | |||
5634 | slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines | ||
5635 | snapDuration: null, // granularity of time for dragging and selecting | ||
5636 | |||
5637 | minTime: null, // Duration object that denotes the first visible time of any given day | ||
5638 | maxTime: null, // Duration object that denotes the exclusive visible end time of any given day | ||
5639 | |||
5640 | axisFormat: null, // formatting string for times running along vertical axis | ||
5641 | |||
5642 | dayEls: null, // cells elements in the day-row background | ||
5643 | slatEls: null, // elements running horizontally across all columns | ||
5644 | |||
5645 | slatTops: null, // an array of top positions, relative to the container. last item holds bottom of last slot | ||
5646 | |||
5647 | helperEl: null, // cell skeleton element for rendering the mock event "helper" | ||
5648 | |||
5649 | businessHourSegs: null, | ||
5650 | |||
5651 | |||
5652 | constructor: function() { | ||
5653 | Grid.apply(this, arguments); // call the super-constructor | ||
5654 | this.processOptions(); | ||
5655 | }, | ||
5656 | |||
5657 | |||
5658 | // Renders the time grid into `this.el`, which should already be assigned. | ||
5659 | // Relies on the view's colCnt. In the future, this component should probably be self-sufficient. | ||
5660 | renderDates: function() { | ||
5661 | this.el.html(this.renderHtml()); | ||
5662 | this.dayEls = this.el.find('.fc-day'); | ||
5663 | this.slatEls = this.el.find('.fc-slats tr'); | ||
5664 | }, | ||
5665 | |||
5666 | |||
5667 | renderBusinessHours: function() { | ||
5668 | var events = this.view.calendar.getBusinessHoursEvents(); | ||
5669 | this.businessHourSegs = this.renderFill('businessHours', this.eventsToSegs(events), 'bgevent'); | ||
5670 | }, | ||
5671 | |||
5672 | |||
5673 | // Renders the basic HTML skeleton for the grid | ||
5674 | renderHtml: function() { | ||
5675 | return '' + | ||
5676 | '<div class="fc-bg">' + | ||
5677 | '<table>' + | ||
5678 | this.rowHtml('slotBg') + // leverages RowRenderer, which will call slotBgCellHtml | ||
5679 | '</table>' + | ||
5680 | '</div>' + | ||
5681 | '<div class="fc-slats">' + | ||
5682 | '<table>' + | ||
5683 | this.slatRowHtml() + | ||
5684 | '</table>' + | ||
5685 | '</div>'; | ||
5686 | }, | ||
5687 | |||
5688 | |||
5689 | // Renders the HTML for a vertical background cell behind the slots. | ||
5690 | // This method is distinct from 'bg' because we wanted a new `rowType` so the View could customize the rendering. | ||
5691 | slotBgCellHtml: function(cell) { | ||
5692 | return this.bgCellHtml(cell); | ||
5693 | }, | ||
5694 | |||
5695 | |||
5696 | // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. | ||
5697 | slatRowHtml: function() { | ||
5698 | var view = this.view; | ||
5699 | var isRTL = this.isRTL; | ||
5700 | var html = ''; | ||
5701 | var slotNormal = this.slotDuration.asMinutes() % 15 === 0; | ||
5702 | var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations | ||
5703 | var slotDate; // will be on the view's first day, but we only care about its time | ||
5704 | var minutes; | ||
5705 | var axisHtml; | ||
5706 | |||
5707 | // Calculate the time for each slot | ||
5708 | while (slotTime < this.maxTime) { | ||
5709 | slotDate = this.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues | ||
5710 | minutes = slotDate.minutes(); | ||
5711 | |||
5712 | axisHtml = | ||
5713 | '<td class="fc-axis fc-time ' + view.widgetContentClass + '" ' + view.axisStyleAttr() + '>' + | ||
5714 | ((!slotNormal || !minutes) ? // if irregular slot duration, or on the hour, then display the time | ||
5715 | '<span>' + // for matchCellWidths | ||
5716 | htmlEscape(slotDate.format(this.axisFormat)) + | ||
5717 | '</span>' : | ||
5718 | '' | ||
5719 | ) + | ||
5720 | '</td>'; | ||
5721 | |||
5722 | html += | ||
5723 | '<tr ' + (!minutes ? '' : 'class="fc-minor"') + '>' + | ||
5724 | (!isRTL ? axisHtml : '') + | ||
5725 | '<td class="' + view.widgetContentClass + '"/>' + | ||
5726 | (isRTL ? axisHtml : '') + | ||
5727 | "</tr>"; | ||
5728 | |||
5729 | slotTime.add(this.slotDuration); | ||
5730 | } | ||
5731 | |||
5732 | return html; | ||
5733 | }, | ||
5734 | |||
5735 | |||
5736 | /* Options | ||
5737 | ------------------------------------------------------------------------------------------------------------------*/ | ||
5738 | |||
5739 | |||
5740 | // Parses various options into properties of this object | ||
5741 | processOptions: function() { | ||
5742 | var view = this.view; | ||
5743 | var slotDuration = view.opt('slotDuration'); | ||
5744 | var snapDuration = view.opt('snapDuration'); | ||
5745 | |||
5746 | slotDuration = moment.duration(slotDuration); | ||
5747 | snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration; | ||
5748 | |||
5749 | this.slotDuration = slotDuration; | ||
5750 | this.snapDuration = snapDuration; | ||
5751 | this.cellDuration = snapDuration; // for Grid system | ||
5752 | |||
5753 | this.minTime = moment.duration(view.opt('minTime')); | ||
5754 | this.maxTime = moment.duration(view.opt('maxTime')); | ||
5755 | |||
5756 | this.axisFormat = view.opt('axisFormat') || view.opt('smallTimeFormat'); | ||
5757 | }, | ||
5758 | |||
5759 | |||
5760 | // Computes a default column header formatting string if `colFormat` is not explicitly defined | ||
5761 | computeColHeadFormat: function() { | ||
5762 | if (this.colCnt > 1) { // multiple days, so full single date string WON'T be in title text | ||
5763 | return this.view.opt('dayOfMonthFormat'); // "Sat 12/10" | ||
5764 | } | ||
5765 | else { // single day, so full single date string will probably be in title text | ||
5766 | return 'dddd'; // "Saturday" | ||
5767 | } | ||
5768 | }, | ||
5769 | |||
5770 | |||
5771 | // Computes a default event time formatting string if `timeFormat` is not explicitly defined | ||
5772 | computeEventTimeFormat: function() { | ||
5773 | return this.view.opt('noMeridiemTimeFormat'); // like "6:30" (no AM/PM) | ||
5774 | }, | ||
5775 | |||
5776 | |||
5777 | // Computes a default `displayEventEnd` value if one is not expliclty defined | ||
5778 | computeDisplayEventEnd: function() { | ||
5779 | return true; | ||
5780 | }, | ||
5781 | |||
5782 | |||
5783 | /* Cell System | ||
5784 | ------------------------------------------------------------------------------------------------------------------*/ | ||
5785 | |||
5786 | |||
5787 | // Initializes row/col information | ||
5788 | updateCells: function() { | ||
5789 | var view = this.view; | ||
5790 | var colData = []; | ||
5791 | var date; | ||
5792 | |||
5793 | date = this.start.clone(); | ||
5794 | while (date.isBefore(this.end)) { | ||
5795 | colData.push({ | ||
5796 | day: date.clone() | ||
5797 | }); | ||
5798 | date.add(1, 'day'); | ||
5799 | date = view.skipHiddenDays(date); | ||
5800 | } | ||
5801 | |||
5802 | if (this.isRTL) { | ||
5803 | colData.reverse(); | ||
5804 | } | ||
5805 | |||
5806 | this.colData = colData; | ||
5807 | this.colCnt = colData.length; | ||
5808 | this.rowCnt = Math.ceil((this.maxTime - this.minTime) / this.snapDuration); // # of vertical snaps | ||
5809 | }, | ||
5810 | |||
5811 | |||
5812 | // Given a cell object, generates its start date. Returns a reference-free copy. | ||
5813 | computeCellDate: function(cell) { | ||
5814 | var time = this.computeSnapTime(cell.row); | ||
5815 | |||
5816 | return this.view.calendar.rezoneDate(cell.day).time(time); | ||
5817 | }, | ||
5818 | |||
5819 | |||
5820 | // Retrieves the element representing the given column | ||
5821 | getColEl: function(col) { | ||
5822 | return this.dayEls.eq(col); | ||
5823 | }, | ||
5824 | |||
5825 | |||
5826 | /* Dates | ||
5827 | ------------------------------------------------------------------------------------------------------------------*/ | ||
5828 | |||
5829 | |||
5830 | // Given a row number of the grid, representing a "snap", returns a time (Duration) from its start-of-day | ||
5831 | computeSnapTime: function(row) { | ||
5832 | return moment.duration(this.minTime + this.snapDuration * row); | ||
5833 | }, | ||
5834 | |||
5835 | |||
5836 | // Slices up a date range by column into an array of segments | ||
5837 | rangeToSegs: function(range) { | ||
5838 | var colCnt = this.colCnt; | ||
5839 | var segs = []; | ||
5840 | var seg; | ||
5841 | var col; | ||
5842 | var colDate; | ||
5843 | var colRange; | ||
5844 | |||
5845 | // normalize :( | ||
5846 | range = { | ||
5847 | start: range.start.clone().stripZone(), | ||
5848 | end: range.end.clone().stripZone() | ||
5849 | }; | ||
5850 | |||
5851 | for (col = 0; col < colCnt; col++) { | ||
5852 | colDate = this.colData[col].day; // will be ambig time/timezone | ||
5853 | colRange = { | ||
5854 | start: colDate.clone().time(this.minTime), | ||
5855 | end: colDate.clone().time(this.maxTime) | ||
5856 | }; | ||
5857 | seg = intersectionToSeg(range, colRange); // both will be ambig timezone | ||
5858 | if (seg) { | ||
5859 | seg.col = col; | ||
5860 | segs.push(seg); | ||
5861 | } | ||
5862 | } | ||
5863 | |||
5864 | return segs; | ||
5865 | }, | ||
5866 | |||
5867 | |||
5868 | /* Coordinates | ||
5869 | ------------------------------------------------------------------------------------------------------------------*/ | ||
5870 | |||
5871 | |||
5872 | updateSize: function(isResize) { // NOT a standard Grid method | ||
5873 | this.computeSlatTops(); | ||
5874 | |||
5875 | if (isResize) { | ||
5876 | this.updateSegVerticals(); | ||
5877 | } | ||
5878 | }, | ||
5879 | |||
5880 | |||
5881 | // Computes the top/bottom coordinates of each "snap" rows | ||
5882 | computeRowCoords: function() { | ||
5883 | var originTop = this.el.offset().top; | ||
5884 | var items = []; | ||
5885 | var i; | ||
5886 | var item; | ||
5887 | |||
5888 | for (i = 0; i < this.rowCnt; i++) { | ||
5889 | item = { | ||
5890 | top: originTop + this.computeTimeTop(this.computeSnapTime(i)) | ||
5891 | }; | ||
5892 | if (i > 0) { | ||
5893 | items[i - 1].bottom = item.top; | ||
5894 | } | ||
5895 | items.push(item); | ||
5896 | } | ||
5897 | item.bottom = item.top + this.computeTimeTop(this.computeSnapTime(i)); | ||
5898 | |||
5899 | return items; | ||
5900 | }, | ||
5901 | |||
5902 | |||
5903 | // Computes the top coordinate, relative to the bounds of the grid, of the given date. | ||
5904 | // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. | ||
5905 | computeDateTop: function(date, startOfDayDate) { | ||
5906 | return this.computeTimeTop( | ||
5907 | moment.duration( | ||
5908 | date.clone().stripZone() - startOfDayDate.clone().stripTime() | ||
5909 | ) | ||
5910 | ); | ||
5911 | }, | ||
5912 | |||
5913 | |||
5914 | // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). | ||
5915 | computeTimeTop: function(time) { | ||
5916 | var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered | ||
5917 | var slatIndex; | ||
5918 | var slatRemainder; | ||
5919 | var slatTop; | ||
5920 | var slatBottom; | ||
5921 | |||
5922 | // constrain. because minTime/maxTime might be customized | ||
5923 | slatCoverage = Math.max(0, slatCoverage); | ||
5924 | slatCoverage = Math.min(this.slatEls.length, slatCoverage); | ||
5925 | |||
5926 | slatIndex = Math.floor(slatCoverage); // an integer index of the furthest whole slot | ||
5927 | slatRemainder = slatCoverage - slatIndex; | ||
5928 | slatTop = this.slatTops[slatIndex]; // the top position of the furthest whole slot | ||
5929 | |||
5930 | if (slatRemainder) { // time spans part-way into the slot | ||
5931 | slatBottom = this.slatTops[slatIndex + 1]; | ||
5932 | return slatTop + (slatBottom - slatTop) * slatRemainder; // part-way between slots | ||
5933 | } | ||
5934 | else { | ||
5935 | return slatTop; | ||
5936 | } | ||
5937 | }, | ||
5938 | |||
5939 | |||
5940 | // Queries each `slatEl` for its position relative to the grid's container and stores it in `slatTops`. | ||
5941 | // Includes the the bottom of the last slat as the last item in the array. | ||
5942 | computeSlatTops: function() { | ||
5943 | var tops = []; | ||
5944 | var top; | ||
5945 | |||
5946 | this.slatEls.each(function(i, node) { | ||
5947 | top = $(node).position().top; | ||
5948 | tops.push(top); | ||
5949 | }); | ||
5950 | |||
5951 | tops.push(top + this.slatEls.last().outerHeight()); // bottom of the last slat | ||
5952 | |||
5953 | this.slatTops = tops; | ||
5954 | }, | ||
5955 | |||
5956 | |||
5957 | /* Event Drag Visualization | ||
5958 | ------------------------------------------------------------------------------------------------------------------*/ | ||
5959 | |||
5960 | |||
5961 | // Renders a visual indication of an event being dragged over the specified date(s). | ||
5962 | // dropLocation's end might be null, as well as `seg`. See Grid::renderDrag for more info. | ||
5963 | // A returned value of `true` signals that a mock "helper" event has been rendered. | ||
5964 | renderDrag: function(dropLocation, seg) { | ||
5965 | |||
5966 | if (seg) { // if there is event information for this drag, render a helper event | ||
5967 | this.renderRangeHelper(dropLocation, seg); | ||
5968 | this.applyDragOpacity(this.helperEl); | ||
5969 | |||
5970 | return true; // signal that a helper has been rendered | ||
5971 | } | ||
5972 | else { | ||
5973 | // otherwise, just render a highlight | ||
5974 | this.renderHighlight( | ||
5975 | this.view.calendar.ensureVisibleEventRange(dropLocation) // needs to be a proper range | ||
5976 | ); | ||
5977 | } | ||
5978 | }, | ||
5979 | |||
5980 | |||
5981 | // Unrenders any visual indication of an event being dragged | ||
5982 | destroyDrag: function() { | ||
5983 | this.destroyHelper(); | ||
5984 | this.destroyHighlight(); | ||
5985 | }, | ||
5986 | |||
5987 | |||
5988 | /* Event Resize Visualization | ||
5989 | ------------------------------------------------------------------------------------------------------------------*/ | ||
5990 | |||
5991 | |||
5992 | // Renders a visual indication of an event being resized | ||
5993 | renderEventResize: function(range, seg) { | ||
5994 | this.renderRangeHelper(range, seg); | ||
5995 | }, | ||
5996 | |||
5997 | |||
5998 | // Unrenders any visual indication of an event being resized | ||
5999 | destroyEventResize: function() { | ||
6000 | this.destroyHelper(); | ||
6001 | }, | ||
6002 | |||
6003 | |||
6004 | /* Event Helper | ||
6005 | ------------------------------------------------------------------------------------------------------------------*/ | ||
6006 | |||
6007 | |||
6008 | // Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag) | ||
6009 | renderHelper: function(event, sourceSeg) { | ||
6010 | var segs = this.eventsToSegs([ event ]); | ||
6011 | var tableEl; | ||
6012 | var i, seg; | ||
6013 | var sourceEl; | ||
6014 | |||
6015 | segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered | ||
6016 | tableEl = this.renderSegTable(segs); | ||
6017 | |||
6018 | // Try to make the segment that is in the same row as sourceSeg look the same | ||
6019 | for (i = 0; i < segs.length; i++) { | ||
6020 | seg = segs[i]; | ||
6021 | if (sourceSeg && sourceSeg.col === seg.col) { | ||
6022 | sourceEl = sourceSeg.el; | ||
6023 | seg.el.css({ | ||
6024 | left: sourceEl.css('left'), | ||
6025 | right: sourceEl.css('right'), | ||
6026 | 'margin-left': sourceEl.css('margin-left'), | ||
6027 | 'margin-right': sourceEl.css('margin-right') | ||
6028 | }); | ||
6029 | } | ||
6030 | } | ||
6031 | |||
6032 | this.helperEl = $('<div class="fc-helper-skeleton"/>') | ||
6033 | .append(tableEl) | ||
6034 | .appendTo(this.el); | ||
6035 | }, | ||
6036 | |||
6037 | |||
6038 | // Unrenders any mock helper event | ||
6039 | destroyHelper: function() { | ||
6040 | if (this.helperEl) { | ||
6041 | this.helperEl.remove(); | ||
6042 | this.helperEl = null; | ||
6043 | } | ||
6044 | }, | ||
6045 | |||
6046 | |||
6047 | /* Selection | ||
6048 | ------------------------------------------------------------------------------------------------------------------*/ | ||
6049 | |||
6050 | |||
6051 | // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight. | ||
6052 | renderSelection: function(range) { | ||
6053 | if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered | ||
6054 | this.renderRangeHelper(range); | ||
6055 | } | ||
6056 | else { | ||
6057 | this.renderHighlight(range); | ||
6058 | } | ||
6059 | }, | ||
6060 | |||
6061 | |||
6062 | // Unrenders any visual indication of a selection | ||
6063 | destroySelection: function() { | ||
6064 | this.destroyHelper(); | ||
6065 | this.destroyHighlight(); | ||
6066 | }, | ||
6067 | |||
6068 | |||
6069 | /* Fill System (highlight, background events, business hours) | ||
6070 | ------------------------------------------------------------------------------------------------------------------*/ | ||
6071 | |||
6072 | |||
6073 | // Renders a set of rectangles over the given time segments. | ||
6074 | // Only returns segments that successfully rendered. | ||
6075 | renderFill: function(type, segs, className) { | ||
6076 | var segCols; | ||
6077 | var skeletonEl; | ||
6078 | var trEl; | ||
6079 | var col, colSegs; | ||
6080 | var tdEl; | ||
6081 | var containerEl; | ||
6082 | var dayDate; | ||
6083 | var i, seg; | ||
6084 | |||
6085 | if (segs.length) { | ||
6086 | |||
6087 | segs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs | ||
6088 | segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg | ||
6089 | |||
6090 | className = className || type.toLowerCase(); | ||
6091 | skeletonEl = $( | ||
6092 | '<div class="fc-' + className + '-skeleton">' + | ||
6093 | '<table><tr/></table>' + | ||
6094 | '</div>' | ||
6095 | ); | ||
6096 | trEl = skeletonEl.find('tr'); | ||
6097 | |||
6098 | for (col = 0; col < segCols.length; col++) { | ||
6099 | colSegs = segCols[col]; | ||
6100 | tdEl = $('<td/>').appendTo(trEl); | ||
6101 | |||
6102 | if (colSegs.length) { | ||
6103 | containerEl = $('<div class="fc-' + className + '-container"/>').appendTo(tdEl); | ||
6104 | dayDate = this.colData[col].day; | ||
6105 | |||
6106 | for (i = 0; i < colSegs.length; i++) { | ||
6107 | seg = colSegs[i]; | ||
6108 | containerEl.append( | ||
6109 | seg.el.css({ | ||
6110 | top: this.computeDateTop(seg.start, dayDate), | ||
6111 | bottom: -this.computeDateTop(seg.end, dayDate) // the y position of the bottom edge | ||
6112 | }) | ||
6113 | ); | ||
6114 | } | ||
6115 | } | ||
6116 | } | ||
6117 | |||
6118 | this.bookendCells(trEl, type); | ||
6119 | |||
6120 | this.el.append(skeletonEl); | ||
6121 | this.elsByFill[type] = skeletonEl; | ||
6122 | } | ||
6123 | |||
6124 | return segs; | ||
6125 | } | ||
6126 | |||
6127 | }); | ||
6128 | |||
6129 | ;; | ||
6130 | |||
6131 | /* Event-rendering methods for the TimeGrid class | ||
6132 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
6133 | |||
6134 | TimeGrid.mixin({ | ||
6135 | |||
6136 | eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements | ||
6137 | |||
6138 | |||
6139 | // Renders the given foreground event segments onto the grid | ||
6140 | renderFgSegs: function(segs) { | ||
6141 | segs = this.renderFgSegEls(segs); // returns a subset of the segs. segs that were actually rendered | ||
6142 | |||
6143 | this.el.append( | ||
6144 | this.eventSkeletonEl = $('<div class="fc-content-skeleton"/>') | ||
6145 | .append(this.renderSegTable(segs)) | ||
6146 | ); | ||
6147 | |||
6148 | return segs; // return only the segs that were actually rendered | ||
6149 | }, | ||
6150 | |||
6151 | |||
6152 | // Unrenders all currently rendered foreground event segments | ||
6153 | destroyFgSegs: function(segs) { | ||
6154 | if (this.eventSkeletonEl) { | ||
6155 | this.eventSkeletonEl.remove(); | ||
6156 | this.eventSkeletonEl = null; | ||
6157 | } | ||
6158 | }, | ||
6159 | |||
6160 | |||
6161 | // Renders and returns the <table> portion of the event-skeleton. | ||
6162 | // Returns an object with properties 'tbodyEl' and 'segs'. | ||
6163 | renderSegTable: function(segs) { | ||
6164 | var tableEl = $('<table><tr/></table>'); | ||
6165 | var trEl = tableEl.find('tr'); | ||
6166 | var segCols; | ||
6167 | var i, seg; | ||
6168 | var col, colSegs; | ||
6169 | var containerEl; | ||
6170 | |||
6171 | segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg | ||
6172 | |||
6173 | this.computeSegVerticals(segs); // compute and assign top/bottom | ||
6174 | |||
6175 | for (col = 0; col < segCols.length; col++) { // iterate each column grouping | ||
6176 | colSegs = segCols[col]; | ||
6177 | placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array | ||
6178 | |||
6179 | containerEl = $('<div class="fc-event-container"/>'); | ||
6180 | |||
6181 | // assign positioning CSS and insert into container | ||
6182 | for (i = 0; i < colSegs.length; i++) { | ||
6183 | seg = colSegs[i]; | ||
6184 | seg.el.css(this.generateSegPositionCss(seg)); | ||
6185 | |||
6186 | // if the height is short, add a className for alternate styling | ||
6187 | if (seg.bottom - seg.top < 30) { | ||
6188 | seg.el.addClass('fc-short'); | ||
6189 | } | ||
6190 | |||
6191 | containerEl.append(seg.el); | ||
6192 | } | ||
6193 | |||
6194 | trEl.append($('<td/>').append(containerEl)); | ||
6195 | } | ||
6196 | |||
6197 | this.bookendCells(trEl, 'eventSkeleton'); | ||
6198 | |||
6199 | return tableEl; | ||
6200 | }, | ||
6201 | |||
6202 | |||
6203 | // Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom. | ||
6204 | // Repositions business hours segs too, so not just for events. Maybe shouldn't be here. | ||
6205 | updateSegVerticals: function() { | ||
6206 | var allSegs = (this.segs || []).concat(this.businessHourSegs || []); | ||
6207 | var i; | ||
6208 | |||
6209 | this.computeSegVerticals(allSegs); | ||
6210 | |||
6211 | for (i = 0; i < allSegs.length; i++) { | ||
6212 | allSegs[i].el.css( | ||
6213 | this.generateSegVerticalCss(allSegs[i]) | ||
6214 | ); | ||
6215 | } | ||
6216 | }, | ||
6217 | |||
6218 | |||
6219 | // For each segment in an array, computes and assigns its top and bottom properties | ||
6220 | computeSegVerticals: function(segs) { | ||
6221 | var i, seg; | ||
6222 | |||
6223 | for (i = 0; i < segs.length; i++) { | ||
6224 | seg = segs[i]; | ||
6225 | seg.top = this.computeDateTop(seg.start, seg.start); | ||
6226 | seg.bottom = this.computeDateTop(seg.end, seg.start); | ||
6227 | } | ||
6228 | }, | ||
6229 | |||
6230 | |||
6231 | // Renders the HTML for a single event segment's default rendering | ||
6232 | fgSegHtml: function(seg, disableResizing) { | ||
6233 | var view = this.view; | ||
6234 | var event = seg.event; | ||
6235 | var isDraggable = view.isEventDraggable(event); | ||
6236 | var isResizableFromStart = !disableResizing && seg.isStart && view.isEventResizableFromStart(event); | ||
6237 | var isResizableFromEnd = !disableResizing && seg.isEnd && view.isEventResizableFromEnd(event); | ||
6238 | var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd); | ||
6239 | var skinCss = cssToStr(this.getEventSkinCss(event)); | ||
6240 | var timeText; | ||
6241 | var fullTimeText; // more verbose time text. for the print stylesheet | ||
6242 | var startTimeText; // just the start time text | ||
6243 | |||
6244 | classes.unshift('fc-time-grid-event', 'fc-v-event'); | ||
6245 | |||
6246 | if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day... | ||
6247 | // Don't display time text on segments that run entirely through a day. | ||
6248 | // That would appear as midnight-midnight and would look dumb. | ||
6249 | // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am) | ||
6250 | if (seg.isStart || seg.isEnd) { | ||
6251 | timeText = this.getEventTimeText(seg); | ||
6252 | fullTimeText = this.getEventTimeText(seg, 'LT'); | ||
6253 | startTimeText = this.getEventTimeText(seg, null, false); // displayEnd=false | ||
6254 | } | ||
6255 | } else { | ||
6256 | // Display the normal time text for the *event's* times | ||
6257 | timeText = this.getEventTimeText(event); | ||
6258 | fullTimeText = this.getEventTimeText(event, 'LT'); | ||
6259 | startTimeText = this.getEventTimeText(event, null, false); // displayEnd=false | ||
6260 | } | ||
6261 | |||
6262 | return '<a class="' + classes.join(' ') + '"' + | ||
6263 | (event.url ? | ||
6264 | ' href="' + htmlEscape(event.url) + '"' : | ||
6265 | '' | ||
6266 | ) + | ||
6267 | (skinCss ? | ||
6268 | ' style="' + skinCss + '"' : | ||
6269 | '' | ||
6270 | ) + | ||
6271 | '>' + | ||
6272 | '<div class="fc-content">' + | ||
6273 | (timeText ? | ||
6274 | '<div class="fc-time"' + | ||
6275 | ' data-start="' + htmlEscape(startTimeText) + '"' + | ||
6276 | ' data-full="' + htmlEscape(fullTimeText) + '"' + | ||
6277 | '>' + | ||
6278 | '<span>' + htmlEscape(timeText) + '</span>' + | ||
6279 | '</div>' : | ||
6280 | '' | ||
6281 | ) + | ||
6282 | (event.title ? | ||
6283 | '<div class="fc-title">' + | ||
6284 | htmlEscape(event.title) + | ||
6285 | '</div>' : | ||
6286 | '' | ||
6287 | ) + | ||
6288 | '</div>' + | ||
6289 | '<div class="fc-bg"/>' + | ||
6290 | /* TODO: write CSS for this | ||
6291 | (isResizableFromStart ? | ||
6292 | '<div class="fc-resizer fc-start-resizer" />' : | ||
6293 | '' | ||
6294 | ) + | ||
6295 | */ | ||
6296 | (isResizableFromEnd ? | ||
6297 | '<div class="fc-resizer fc-end-resizer" />' : | ||
6298 | '' | ||
6299 | ) + | ||
6300 | '</a>'; | ||
6301 | }, | ||
6302 | |||
6303 | |||
6304 | // Generates an object with CSS properties/values that should be applied to an event segment element. | ||
6305 | // Contains important positioning-related properties that should be applied to any event element, customized or not. | ||
6306 | generateSegPositionCss: function(seg) { | ||
6307 | var shouldOverlap = this.view.opt('slotEventOverlap'); | ||
6308 | var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point | ||
6309 | var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point | ||
6310 | var props = this.generateSegVerticalCss(seg); // get top/bottom first | ||
6311 | var left; // amount of space from left edge, a fraction of the total width | ||
6312 | var right; // amount of space from right edge, a fraction of the total width | ||
6313 | |||
6314 | if (shouldOverlap) { | ||
6315 | // double the width, but don't go beyond the maximum forward coordinate (1.0) | ||
6316 | forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2); | ||
6317 | } | ||
6318 | |||
6319 | if (this.isRTL) { | ||
6320 | left = 1 - forwardCoord; | ||
6321 | right = backwardCoord; | ||
6322 | } | ||
6323 | else { | ||
6324 | left = backwardCoord; | ||
6325 | right = 1 - forwardCoord; | ||
6326 | } | ||
6327 | |||
6328 | props.zIndex = seg.level + 1; // convert from 0-base to 1-based | ||
6329 | props.left = left * 100 + '%'; | ||
6330 | props.right = right * 100 + '%'; | ||
6331 | |||
6332 | if (shouldOverlap && seg.forwardPressure) { | ||
6333 | // add padding to the edge so that forward stacked events don't cover the resizer's icon | ||
6334 | props[this.isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width | ||
6335 | } | ||
6336 | |||
6337 | return props; | ||
6338 | }, | ||
6339 | |||
6340 | |||
6341 | // Generates an object with CSS properties for the top/bottom coordinates of a segment element | ||
6342 | generateSegVerticalCss: function(seg) { | ||
6343 | return { | ||
6344 | top: seg.top, | ||
6345 | bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container | ||
6346 | }; | ||
6347 | }, | ||
6348 | |||
6349 | |||
6350 | // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col | ||
6351 | groupSegCols: function(segs) { | ||
6352 | var segCols = []; | ||
6353 | var i; | ||
6354 | |||
6355 | for (i = 0; i < this.colCnt; i++) { | ||
6356 | segCols.push([]); | ||
6357 | } | ||
6358 | |||
6359 | for (i = 0; i < segs.length; i++) { | ||
6360 | segCols[segs[i].col].push(segs[i]); | ||
6361 | } | ||
6362 | |||
6363 | return segCols; | ||
6364 | } | ||
6365 | |||
6366 | }); | ||
6367 | |||
6368 | |||
6369 | // Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each. | ||
6370 | // NOTE: Also reorders the given array by date! | ||
6371 | function placeSlotSegs(segs) { | ||
6372 | var levels; | ||
6373 | var level0; | ||
6374 | var i; | ||
6375 | |||
6376 | segs.sort(compareSegs); // order by date | ||
6377 | levels = buildSlotSegLevels(segs); | ||
6378 | computeForwardSlotSegs(levels); | ||
6379 | |||
6380 | if ((level0 = levels[0])) { | ||
6381 | |||
6382 | for (i = 0; i < level0.length; i++) { | ||
6383 | computeSlotSegPressures(level0[i]); | ||
6384 | } | ||
6385 | |||
6386 | for (i = 0; i < level0.length; i++) { | ||
6387 | computeSlotSegCoords(level0[i], 0, 0); | ||
6388 | } | ||
6389 | } | ||
6390 | } | ||
6391 | |||
6392 | |||
6393 | // Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is | ||
6394 | // left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date. | ||
6395 | function buildSlotSegLevels(segs) { | ||
6396 | var levels = []; | ||
6397 | var i, seg; | ||
6398 | var j; | ||
6399 | |||
6400 | for (i=0; i<segs.length; i++) { | ||
6401 | seg = segs[i]; | ||
6402 | |||
6403 | // go through all the levels and stop on the first level where there are no collisions | ||
6404 | for (j=0; j<levels.length; j++) { | ||
6405 | if (!computeSlotSegCollisions(seg, levels[j]).length) { | ||
6406 | break; | ||
6407 | } | ||
6408 | } | ||
6409 | |||
6410 | seg.level = j; | ||
6411 | |||
6412 | (levels[j] || (levels[j] = [])).push(seg); | ||
6413 | } | ||
6414 | |||
6415 | return levels; | ||
6416 | } | ||
6417 | |||
6418 | |||
6419 | // For every segment, figure out the other segments that are in subsequent | ||
6420 | // levels that also occupy the same vertical space. Accumulate in seg.forwardSegs | ||
6421 | function computeForwardSlotSegs(levels) { | ||
6422 | var i, level; | ||
6423 | var j, seg; | ||
6424 | var k; | ||
6425 | |||
6426 | for (i=0; i<levels.length; i++) { | ||
6427 | level = levels[i]; | ||
6428 | |||
6429 | for (j=0; j<level.length; j++) { | ||
6430 | seg = level[j]; | ||
6431 | |||
6432 | seg.forwardSegs = []; | ||
6433 | for (k=i+1; k<levels.length; k++) { | ||
6434 | computeSlotSegCollisions(seg, levels[k], seg.forwardSegs); | ||
6435 | } | ||
6436 | } | ||
6437 | } | ||
6438 | } | ||
6439 | |||
6440 | |||
6441 | // Figure out which path forward (via seg.forwardSegs) results in the longest path until | ||
6442 | // the furthest edge is reached. The number of segments in this path will be seg.forwardPressure | ||
6443 | function computeSlotSegPressures(seg) { | ||
6444 | var forwardSegs = seg.forwardSegs; | ||
6445 | var forwardPressure = 0; | ||
6446 | var i, forwardSeg; | ||
6447 | |||
6448 | if (seg.forwardPressure === undefined) { // not already computed | ||
6449 | |||
6450 | for (i=0; i<forwardSegs.length; i++) { | ||
6451 | forwardSeg = forwardSegs[i]; | ||
6452 | |||
6453 | // figure out the child's maximum forward path | ||
6454 | computeSlotSegPressures(forwardSeg); | ||
6455 | |||
6456 | // either use the existing maximum, or use the child's forward pressure | ||
6457 | // plus one (for the forwardSeg itself) | ||
6458 | forwardPressure = Math.max( | ||
6459 | forwardPressure, | ||
6460 | 1 + forwardSeg.forwardPressure | ||
6461 | ); | ||
6462 | } | ||
6463 | |||
6464 | seg.forwardPressure = forwardPressure; | ||
6465 | } | ||
6466 | } | ||
6467 | |||
6468 | |||
6469 | // Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range | ||
6470 | // from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and | ||
6471 | // seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left. | ||
6472 | // | ||
6473 | // The segment might be part of a "series", which means consecutive segments with the same pressure | ||
6474 | // who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of | ||
6475 | // segments behind this one in the current series, and `seriesBackwardCoord` is the starting | ||
6476 | // coordinate of the first segment in the series. | ||
6477 | function computeSlotSegCoords(seg, seriesBackwardPressure, seriesBackwardCoord) { | ||
6478 | var forwardSegs = seg.forwardSegs; | ||
6479 | var i; | ||
6480 | |||
6481 | if (seg.forwardCoord === undefined) { // not already computed | ||
6482 | |||
6483 | if (!forwardSegs.length) { | ||
6484 | |||
6485 | // if there are no forward segments, this segment should butt up against the edge | ||
6486 | seg.forwardCoord = 1; | ||
6487 | } | ||
6488 | else { | ||
6489 | |||
6490 | // sort highest pressure first | ||
6491 | forwardSegs.sort(compareForwardSlotSegs); | ||
6492 | |||
6493 | // this segment's forwardCoord will be calculated from the backwardCoord of the | ||
6494 | // highest-pressure forward segment. | ||
6495 | computeSlotSegCoords(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord); | ||
6496 | seg.forwardCoord = forwardSegs[0].backwardCoord; | ||
6497 | } | ||
6498 | |||
6499 | // calculate the backwardCoord from the forwardCoord. consider the series | ||
6500 | seg.backwardCoord = seg.forwardCoord - | ||
6501 | (seg.forwardCoord - seriesBackwardCoord) / // available width for series | ||
6502 | (seriesBackwardPressure + 1); // # of segments in the series | ||
6503 | |||
6504 | // use this segment's coordinates to computed the coordinates of the less-pressurized | ||
6505 | // forward segments | ||
6506 | for (i=0; i<forwardSegs.length; i++) { | ||
6507 | computeSlotSegCoords(forwardSegs[i], 0, seg.forwardCoord); | ||
6508 | } | ||
6509 | } | ||
6510 | } | ||
6511 | |||
6512 | |||
6513 | // Find all the segments in `otherSegs` that vertically collide with `seg`. | ||
6514 | // Append into an optionally-supplied `results` array and return. | ||
6515 | function computeSlotSegCollisions(seg, otherSegs, results) { | ||
6516 | results = results || []; | ||
6517 | |||
6518 | for (var i=0; i<otherSegs.length; i++) { | ||
6519 | if (isSlotSegCollision(seg, otherSegs[i])) { | ||
6520 | results.push(otherSegs[i]); | ||
6521 | } | ||
6522 | } | ||
6523 | |||
6524 | return results; | ||
6525 | } | ||
6526 | |||
6527 | |||
6528 | // Do these segments occupy the same vertical space? | ||
6529 | function isSlotSegCollision(seg1, seg2) { | ||
6530 | return seg1.bottom > seg2.top && seg1.top < seg2.bottom; | ||
6531 | } | ||
6532 | |||
6533 | |||
6534 | // A cmp function for determining which forward segment to rely on more when computing coordinates. | ||
6535 | function compareForwardSlotSegs(seg1, seg2) { | ||
6536 | // put higher-pressure first | ||
6537 | return seg2.forwardPressure - seg1.forwardPressure || | ||
6538 | // put segments that are closer to initial edge first (and favor ones with no coords yet) | ||
6539 | (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) || | ||
6540 | // do normal sorting... | ||
6541 | compareSegs(seg1, seg2); | ||
6542 | } | ||
6543 | |||
6544 | ;; | ||
6545 | |||
6546 | /* An abstract class from which other views inherit from | ||
6547 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
6548 | |||
6549 | var View = fc.View = Class.extend({ | ||
6550 | |||
6551 | type: null, // subclass' view name (string) | ||
6552 | name: null, // deprecated. use `type` instead | ||
6553 | title: null, // the text that will be displayed in the header's title | ||
6554 | |||
6555 | calendar: null, // owner Calendar object | ||
6556 | options: null, // hash containing all options. already merged with view-specific-options | ||
6557 | coordMap: null, // a CoordMap object for converting pixel regions to dates | ||
6558 | el: null, // the view's containing element. set by Calendar | ||
6559 | |||
6560 | isDisplayed: false, | ||
6561 | isSkeletonRendered: false, | ||
6562 | isEventsRendered: false, | ||
6563 | |||
6564 | // range the view is actually displaying (moments) | ||
6565 | start: null, | ||
6566 | end: null, // exclusive | ||
6567 | |||
6568 | // range the view is formally responsible for (moments) | ||
6569 | // may be different from start/end. for example, a month view might have 1st-31st, excluding padded dates | ||
6570 | intervalStart: null, | ||
6571 | intervalEnd: null, // exclusive | ||
6572 | intervalDuration: null, | ||
6573 | intervalUnit: null, // name of largest unit being displayed, like "month" or "week" | ||
6574 | |||
6575 | isSelected: false, // boolean whether a range of time is user-selected or not | ||
6576 | |||
6577 | // subclasses can optionally use a scroll container | ||
6578 | scrollerEl: null, // the element that will most likely scroll when content is too tall | ||
6579 | scrollTop: null, // cached vertical scroll value | ||
6580 | |||
6581 | // classNames styled by jqui themes | ||
6582 | widgetHeaderClass: null, | ||
6583 | widgetContentClass: null, | ||
6584 | highlightStateClass: null, | ||
6585 | |||
6586 | // for date utils, computed from options | ||
6587 | nextDayThreshold: null, | ||
6588 | isHiddenDayHash: null, | ||
6589 | |||
6590 | // document handlers, bound to `this` object | ||
6591 | documentMousedownProxy: null, // TODO: doesn't work with touch | ||
6592 | |||
6593 | |||
6594 | constructor: function(calendar, type, options, intervalDuration) { | ||
6595 | |||
6596 | this.calendar = calendar; | ||
6597 | this.type = this.name = type; // .name is deprecated | ||
6598 | this.options = options; | ||
6599 | this.intervalDuration = intervalDuration || moment.duration(1, 'day'); | ||
6600 | |||
6601 | this.nextDayThreshold = moment.duration(this.opt('nextDayThreshold')); | ||
6602 | this.initThemingProps(); | ||
6603 | this.initHiddenDays(); | ||
6604 | |||
6605 | this.documentMousedownProxy = proxy(this, 'documentMousedown'); | ||
6606 | |||
6607 | this.initialize(); | ||
6608 | }, | ||
6609 | |||
6610 | |||
6611 | // A good place for subclasses to initialize member variables | ||
6612 | initialize: function() { | ||
6613 | // subclasses can implement | ||
6614 | }, | ||
6615 | |||
6616 | |||
6617 | // Retrieves an option with the given name | ||
6618 | opt: function(name) { | ||
6619 | return this.options[name]; | ||
6620 | }, | ||
6621 | |||
6622 | |||
6623 | // Triggers handlers that are view-related. Modifies args before passing to calendar. | ||
6624 | trigger: function(name, thisObj) { // arguments beyond thisObj are passed along | ||
6625 | var calendar = this.calendar; | ||
6626 | |||
6627 | return calendar.trigger.apply( | ||
6628 | calendar, | ||
6629 | [name, thisObj || this].concat( | ||
6630 | Array.prototype.slice.call(arguments, 2), // arguments beyond thisObj | ||
6631 | [ this ] // always make the last argument a reference to the view. TODO: deprecate | ||
6632 | ) | ||
6633 | ); | ||
6634 | }, | ||
6635 | |||
6636 | |||
6637 | /* Dates | ||
6638 | ------------------------------------------------------------------------------------------------------------------*/ | ||
6639 | |||
6640 | |||
6641 | // Updates all internal dates to center around the given current date | ||
6642 | setDate: function(date) { | ||
6643 | this.setRange(this.computeRange(date)); | ||
6644 | }, | ||
6645 | |||
6646 | |||
6647 | // Updates all internal dates for displaying the given range. | ||
6648 | // Expects all values to be normalized (like what computeRange does). | ||
6649 | setRange: function(range) { | ||
6650 | $.extend(this, range); | ||
6651 | this.updateTitle(); | ||
6652 | }, | ||
6653 | |||
6654 | |||
6655 | // Given a single current date, produce information about what range to display. | ||
6656 | // Subclasses can override. Must return all properties. | ||
6657 | computeRange: function(date) { | ||
6658 | var intervalUnit = computeIntervalUnit(this.intervalDuration); | ||
6659 | var intervalStart = date.clone().startOf(intervalUnit); | ||
6660 | var intervalEnd = intervalStart.clone().add(this.intervalDuration); | ||
6661 | var start, end; | ||
6662 | |||
6663 | // normalize the range's time-ambiguity | ||
6664 | if (/year|month|week|day/.test(intervalUnit)) { // whole-days? | ||
6665 | intervalStart.stripTime(); | ||
6666 | intervalEnd.stripTime(); | ||
6667 | } | ||
6668 | else { // needs to have a time? | ||
6669 | if (!intervalStart.hasTime()) { | ||
6670 | intervalStart = this.calendar.rezoneDate(intervalStart); // convert to current timezone, with 00:00 | ||
6671 | } | ||
6672 | if (!intervalEnd.hasTime()) { | ||
6673 | intervalEnd = this.calendar.rezoneDate(intervalEnd); // convert to current timezone, with 00:00 | ||
6674 | } | ||
6675 | } | ||
6676 | |||
6677 | start = intervalStart.clone(); | ||
6678 | start = this.skipHiddenDays(start); | ||
6679 | end = intervalEnd.clone(); | ||
6680 | end = this.skipHiddenDays(end, -1, true); // exclusively move backwards | ||
6681 | |||
6682 | return { | ||
6683 | intervalUnit: intervalUnit, | ||
6684 | intervalStart: intervalStart, | ||
6685 | intervalEnd: intervalEnd, | ||
6686 | start: start, | ||
6687 | end: end | ||
6688 | }; | ||
6689 | }, | ||
6690 | |||
6691 | |||
6692 | // Computes the new date when the user hits the prev button, given the current date | ||
6693 | computePrevDate: function(date) { | ||
6694 | return this.massageCurrentDate( | ||
6695 | date.clone().startOf(this.intervalUnit).subtract(this.intervalDuration), -1 | ||
6696 | ); | ||
6697 | }, | ||
6698 | |||
6699 | |||
6700 | // Computes the new date when the user hits the next button, given the current date | ||
6701 | computeNextDate: function(date) { | ||
6702 | return this.massageCurrentDate( | ||
6703 | date.clone().startOf(this.intervalUnit).add(this.intervalDuration) | ||
6704 | ); | ||
6705 | }, | ||
6706 | |||
6707 | |||
6708 | // Given an arbitrarily calculated current date of the calendar, returns a date that is ensured to be completely | ||
6709 | // visible. `direction` is optional and indicates which direction the current date was being | ||
6710 | // incremented or decremented (1 or -1). | ||
6711 | massageCurrentDate: function(date, direction) { | ||
6712 | if (this.intervalDuration.as('days') <= 1) { // if the view displays a single day or smaller | ||
6713 | if (this.isHiddenDay(date)) { | ||
6714 | date = this.skipHiddenDays(date, direction); | ||
6715 | date.startOf('day'); | ||
6716 | } | ||
6717 | } | ||
6718 | |||
6719 | return date; | ||
6720 | }, | ||
6721 | |||
6722 | |||
6723 | /* Title and Date Formatting | ||
6724 | ------------------------------------------------------------------------------------------------------------------*/ | ||
6725 | |||
6726 | |||
6727 | // Sets the view's title property to the most updated computed value | ||
6728 | updateTitle: function() { | ||
6729 | this.title = this.computeTitle(); | ||
6730 | }, | ||
6731 | |||
6732 | |||
6733 | // Computes what the title at the top of the calendar should be for this view | ||
6734 | computeTitle: function() { | ||
6735 | return this.formatRange( | ||
6736 | { start: this.intervalStart, end: this.intervalEnd }, | ||
6737 | this.opt('titleFormat') || this.computeTitleFormat(), | ||
6738 | this.opt('titleRangeSeparator') | ||
6739 | ); | ||
6740 | }, | ||
6741 | |||
6742 | |||
6743 | // Generates the format string that should be used to generate the title for the current date range. | ||
6744 | // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`. | ||
6745 | computeTitleFormat: function() { | ||
6746 | if (this.intervalUnit == 'year') { | ||
6747 | return 'YYYY'; | ||
6748 | } | ||
6749 | else if (this.intervalUnit == 'month') { | ||
6750 | return this.opt('monthYearFormat'); // like "September 2014" | ||
6751 | } | ||
6752 | else if (this.intervalDuration.as('days') > 1) { | ||
6753 | return 'll'; // multi-day range. shorter, like "Sep 9 - 10 2014" | ||
6754 | } | ||
6755 | else { | ||
6756 | return 'LL'; // one day. longer, like "September 9 2014" | ||
6757 | } | ||
6758 | }, | ||
6759 | |||
6760 | |||
6761 | // Utility for formatting a range. Accepts a range object, formatting string, and optional separator. | ||
6762 | // Displays all-day ranges naturally, with an inclusive end. Takes the current isRTL into account. | ||
6763 | formatRange: function(range, formatStr, separator) { | ||
6764 | var end = range.end; | ||
6765 | |||
6766 | if (!end.hasTime()) { // all-day? | ||
6767 | end = end.clone().subtract(1); // convert to inclusive. last ms of previous day | ||
6768 | } | ||
6769 | |||
6770 | return formatRange(range.start, end, formatStr, separator, this.opt('isRTL')); | ||
6771 | }, | ||
6772 | |||
6773 | |||
6774 | /* Rendering | ||
6775 | ------------------------------------------------------------------------------------------------------------------*/ | ||
6776 | |||
6777 | |||
6778 | // Sets the container element that the view should render inside of. | ||
6779 | // Does other DOM-related initializations. | ||
6780 | setElement: function(el) { | ||
6781 | this.el = el; | ||
6782 | this.bindGlobalHandlers(); | ||
6783 | }, | ||
6784 | |||
6785 | |||
6786 | // Removes the view's container element from the DOM, clearing any content beforehand. | ||
6787 | // Undoes any other DOM-related attachments. | ||
6788 | removeElement: function() { | ||
6789 | this.clear(); // clears all content | ||
6790 | |||
6791 | // clean up the skeleton | ||
6792 | if (this.isSkeletonRendered) { | ||
6793 | this.destroySkeleton(); | ||
6794 | this.isSkeletonRendered = false; | ||
6795 | } | ||
6796 | |||
6797 | this.unbindGlobalHandlers(); | ||
6798 | |||
6799 | this.el.remove(); | ||
6800 | |||
6801 | // NOTE: don't null-out this.el in case the View was destroyed within an API callback. | ||
6802 | // We don't null-out the View's other jQuery element references upon destroy, so why should we kill this.el? | ||
6803 | }, | ||
6804 | |||
6805 | |||
6806 | // Does everything necessary to display the view centered around the given date. | ||
6807 | // Does every type of rendering EXCEPT rendering events. | ||
6808 | display: function(date) { | ||
6809 | var scrollState = null; | ||
6810 | |||
6811 | if (this.isDisplayed) { | ||
6812 | scrollState = this.queryScroll(); | ||
6813 | } | ||
6814 | |||
6815 | this.clear(); // clear the old content | ||
6816 | this.setDate(date); | ||
6817 | this.render(); | ||
6818 | this.updateSize(); | ||
6819 | this.renderBusinessHours(); // might need coordinates, so should go after updateSize() | ||
6820 | this.isDisplayed = true; | ||
6821 | |||
6822 | scrollState = this.computeInitialScroll(scrollState); | ||
6823 | this.forceScroll(scrollState); | ||
6824 | |||
6825 | this.triggerRender(); | ||
6826 | }, | ||
6827 | |||
6828 | |||
6829 | // Does everything necessary to clear the content of the view. | ||
6830 | // Clears dates and events. Does not clear the skeleton. | ||
6831 | clear: function() { // clears the view of *content* but not the skeleton | ||
6832 | if (this.isDisplayed) { | ||
6833 | this.unselect(); | ||
6834 | this.clearEvents(); | ||
6835 | this.triggerDestroy(); | ||
6836 | this.destroyBusinessHours(); | ||
6837 | this.destroy(); | ||
6838 | this.isDisplayed = false; | ||
6839 | } | ||
6840 | }, | ||
6841 | |||
6842 | |||
6843 | // Renders the view's date-related content, rendering the view's non-content skeleton if necessary | ||
6844 | render: function() { | ||
6845 | if (!this.isSkeletonRendered) { | ||
6846 | this.renderSkeleton(); | ||
6847 | this.isSkeletonRendered = true; | ||
6848 | } | ||
6849 | this.renderDates(); | ||
6850 | }, | ||
6851 | |||
6852 | |||
6853 | // Unrenders the view's date-related content. | ||
6854 | // Call this instead of destroyDates directly in case the View subclass wants to use a render/destroy pattern | ||
6855 | // where both the skeleton and the content always get rendered/unrendered together. | ||
6856 | destroy: function() { | ||
6857 | this.destroyDates(); | ||
6858 | }, | ||
6859 | |||
6860 | |||
6861 | // Renders the basic structure of the view before any content is rendered | ||
6862 | renderSkeleton: function() { | ||
6863 | // subclasses should implement | ||
6864 | }, | ||
6865 | |||
6866 | |||
6867 | // Unrenders the basic structure of the view | ||
6868 | destroySkeleton: function() { | ||
6869 | // subclasses should implement | ||
6870 | }, | ||
6871 | |||
6872 | |||
6873 | // Renders the view's date-related content (like cells that represent days/times). | ||
6874 | // Assumes setRange has already been called and the skeleton has already been rendered. | ||
6875 | renderDates: function() { | ||
6876 | // subclasses should implement | ||
6877 | }, | ||
6878 | |||
6879 | |||
6880 | // Unrenders the view's date-related content | ||
6881 | destroyDates: function() { | ||
6882 | // subclasses should override | ||
6883 | }, | ||
6884 | |||
6885 | |||
6886 | // Renders business-hours onto the view. Assumes updateSize has already been called. | ||
6887 | renderBusinessHours: function() { | ||
6888 | // subclasses should implement | ||
6889 | }, | ||
6890 | |||
6891 | |||
6892 | // Unrenders previously-rendered business-hours | ||
6893 | destroyBusinessHours: function() { | ||
6894 | // subclasses should implement | ||
6895 | }, | ||
6896 | |||
6897 | |||
6898 | // Signals that the view's content has been rendered | ||
6899 | triggerRender: function() { | ||
6900 | this.trigger('viewRender', this, this, this.el); | ||
6901 | }, | ||
6902 | |||
6903 | |||
6904 | // Signals that the view's content is about to be unrendered | ||
6905 | triggerDestroy: function() { | ||
6906 | this.trigger('viewDestroy', this, this, this.el); | ||
6907 | }, | ||
6908 | |||
6909 | |||
6910 | // Binds DOM handlers to elements that reside outside the view container, such as the document | ||
6911 | bindGlobalHandlers: function() { | ||
6912 | $(document).on('mousedown', this.documentMousedownProxy); | ||
6913 | }, | ||
6914 | |||
6915 | |||
6916 | // Unbinds DOM handlers from elements that reside outside the view container | ||
6917 | unbindGlobalHandlers: function() { | ||
6918 | $(document).off('mousedown', this.documentMousedownProxy); | ||
6919 | }, | ||
6920 | |||
6921 | |||
6922 | // Initializes internal variables related to theming | ||
6923 | initThemingProps: function() { | ||
6924 | var tm = this.opt('theme') ? 'ui' : 'fc'; | ||
6925 | |||
6926 | this.widgetHeaderClass = tm + '-widget-header'; | ||
6927 | this.widgetContentClass = tm + '-widget-content'; | ||
6928 | this.highlightStateClass = tm + '-state-highlight'; | ||
6929 | }, | ||
6930 | |||
6931 | |||
6932 | /* Dimensions | ||
6933 | ------------------------------------------------------------------------------------------------------------------*/ | ||
6934 | |||
6935 | |||
6936 | // Refreshes anything dependant upon sizing of the container element of the grid | ||
6937 | updateSize: function(isResize) { | ||
6938 | var scrollState; | ||
6939 | |||
6940 | if (isResize) { | ||
6941 | scrollState = this.queryScroll(); | ||
6942 | } | ||
6943 | |||
6944 | this.updateHeight(); | ||
6945 | this.updateWidth(); | ||
6946 | |||
6947 | if (isResize) { | ||
6948 | this.setScroll(scrollState); | ||
6949 | } | ||
6950 | }, | ||
6951 | |||
6952 | |||
6953 | // Refreshes the horizontal dimensions of the calendar | ||
6954 | updateWidth: function() { | ||
6955 | // subclasses should implement | ||
6956 | }, | ||
6957 | |||
6958 | |||
6959 | // Refreshes the vertical dimensions of the calendar | ||
6960 | updateHeight: function() { | ||
6961 | var calendar = this.calendar; // we poll the calendar for height information | ||
6962 | |||
6963 | this.setHeight( | ||
6964 | calendar.getSuggestedViewHeight(), | ||
6965 | calendar.isHeightAuto() | ||
6966 | ); | ||
6967 | }, | ||
6968 | |||
6969 | |||
6970 | // Updates the vertical dimensions of the calendar to the specified height. | ||
6971 | // if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height. | ||
6972 | setHeight: function(height, isAuto) { | ||
6973 | // subclasses should implement | ||
6974 | }, | ||
6975 | |||
6976 | |||
6977 | /* Scroller | ||
6978 | ------------------------------------------------------------------------------------------------------------------*/ | ||
6979 | |||
6980 | |||
6981 | // Given the total height of the view, return the number of pixels that should be used for the scroller. | ||
6982 | // Utility for subclasses. | ||
6983 | computeScrollerHeight: function(totalHeight) { | ||
6984 | var scrollerEl = this.scrollerEl; | ||
6985 | var both; | ||
6986 | var otherHeight; // cumulative height of everything that is not the scrollerEl in the view (header+borders) | ||
6987 | |||
6988 | both = this.el.add(scrollerEl); | ||
6989 | |||
6990 | // fuckin IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked | ||
6991 | both.css({ | ||
6992 | position: 'relative', // cause a reflow, which will force fresh dimension recalculation | ||
6993 | left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll | ||
6994 | }); | ||
6995 | otherHeight = this.el.outerHeight() - scrollerEl.height(); // grab the dimensions | ||
6996 | both.css({ position: '', left: '' }); // undo hack | ||
6997 | |||
6998 | return totalHeight - otherHeight; | ||
6999 | }, | ||
7000 | |||
7001 | |||
7002 | // Computes the initial pre-configured scroll state prior to allowing the user to change it. | ||
7003 | // Given the scroll state from the previous rendering. If first time rendering, given null. | ||
7004 | computeInitialScroll: function(previousScrollState) { | ||
7005 | return 0; | ||
7006 | }, | ||
7007 | |||
7008 | |||
7009 | // Retrieves the view's current natural scroll state. Can return an arbitrary format. | ||
7010 | queryScroll: function() { | ||
7011 | if (this.scrollerEl) { | ||
7012 | return this.scrollerEl.scrollTop(); // operates on scrollerEl by default | ||
7013 | } | ||
7014 | }, | ||
7015 | |||
7016 | |||
7017 | // Sets the view's scroll state. Will accept the same format computeInitialScroll and queryScroll produce. | ||
7018 | setScroll: function(scrollState) { | ||
7019 | if (this.scrollerEl) { | ||
7020 | return this.scrollerEl.scrollTop(scrollState); // operates on scrollerEl by default | ||
7021 | } | ||
7022 | }, | ||
7023 | |||
7024 | |||
7025 | // Sets the scroll state, making sure to overcome any predefined scroll value the browser has in mind | ||
7026 | forceScroll: function(scrollState) { | ||
7027 | var _this = this; | ||
7028 | |||
7029 | this.setScroll(scrollState); | ||
7030 | setTimeout(function() { | ||
7031 | _this.setScroll(scrollState); | ||
7032 | }, 0); | ||
7033 | }, | ||
7034 | |||
7035 | |||
7036 | /* Event Elements / Segments | ||
7037 | ------------------------------------------------------------------------------------------------------------------*/ | ||
7038 | |||
7039 | |||
7040 | // Does everything necessary to display the given events onto the current view | ||
7041 | displayEvents: function(events) { | ||
7042 | var scrollState = this.queryScroll(); | ||
7043 | |||
7044 | this.clearEvents(); | ||
7045 | this.renderEvents(events); | ||
7046 | this.isEventsRendered = true; | ||
7047 | this.setScroll(scrollState); | ||
7048 | this.triggerEventRender(); | ||
7049 | }, | ||
7050 | |||
7051 | |||
7052 | // Does everything necessary to clear the view's currently-rendered events | ||
7053 | clearEvents: function() { | ||
7054 | if (this.isEventsRendered) { | ||
7055 | this.triggerEventDestroy(); | ||
7056 | this.destroyEvents(); | ||
7057 | this.isEventsRendered = false; | ||
7058 | } | ||
7059 | }, | ||
7060 | |||
7061 | |||
7062 | // Renders the events onto the view. | ||
7063 | renderEvents: function(events) { | ||
7064 | // subclasses should implement | ||
7065 | }, | ||
7066 | |||
7067 | |||
7068 | // Removes event elements from the view. | ||
7069 | destroyEvents: function() { | ||
7070 | // subclasses should implement | ||
7071 | }, | ||
7072 | |||
7073 | |||
7074 | // Signals that all events have been rendered | ||
7075 | triggerEventRender: function() { | ||
7076 | this.renderedEventSegEach(function(seg) { | ||
7077 | this.trigger('eventAfterRender', seg.event, seg.event, seg.el); | ||
7078 | }); | ||
7079 | this.trigger('eventAfterAllRender'); | ||
7080 | }, | ||
7081 | |||
7082 | |||
7083 | // Signals that all event elements are about to be removed | ||
7084 | triggerEventDestroy: function() { | ||
7085 | this.renderedEventSegEach(function(seg) { | ||
7086 | this.trigger('eventDestroy', seg.event, seg.event, seg.el); | ||
7087 | }); | ||
7088 | }, | ||
7089 | |||
7090 | |||
7091 | // Given an event and the default element used for rendering, returns the element that should actually be used. | ||
7092 | // Basically runs events and elements through the eventRender hook. | ||
7093 | resolveEventEl: function(event, el) { | ||
7094 | var custom = this.trigger('eventRender', event, event, el); | ||
7095 | |||
7096 | if (custom === false) { // means don't render at all | ||
7097 | el = null; | ||
7098 | } | ||
7099 | else if (custom && custom !== true) { | ||
7100 | el = $(custom); | ||
7101 | } | ||
7102 | |||
7103 | return el; | ||
7104 | }, | ||
7105 | |||
7106 | |||
7107 | // Hides all rendered event segments linked to the given event | ||
7108 | showEvent: function(event) { | ||
7109 | this.renderedEventSegEach(function(seg) { | ||
7110 | seg.el.css('visibility', ''); | ||
7111 | }, event); | ||
7112 | }, | ||
7113 | |||
7114 | |||
7115 | // Shows all rendered event segments linked to the given event | ||
7116 | hideEvent: function(event) { | ||
7117 | this.renderedEventSegEach(function(seg) { | ||
7118 | seg.el.css('visibility', 'hidden'); | ||
7119 | }, event); | ||
7120 | }, | ||
7121 | |||
7122 | |||
7123 | // Iterates through event segments that have been rendered (have an el). Goes through all by default. | ||
7124 | // If the optional `event` argument is specified, only iterates through segments linked to that event. | ||
7125 | // The `this` value of the callback function will be the view. | ||
7126 | renderedEventSegEach: function(func, event) { | ||
7127 | var segs = this.getEventSegs(); | ||
7128 | var i; | ||
7129 | |||
7130 | for (i = 0; i < segs.length; i++) { | ||
7131 | if (!event || segs[i].event._id === event._id) { | ||
7132 | if (segs[i].el) { | ||
7133 | func.call(this, segs[i]); | ||
7134 | } | ||
7135 | } | ||
7136 | } | ||
7137 | }, | ||
7138 | |||
7139 | |||
7140 | // Retrieves all the rendered segment objects for the view | ||
7141 | getEventSegs: function() { | ||
7142 | // subclasses must implement | ||
7143 | return []; | ||
7144 | }, | ||
7145 | |||
7146 | |||
7147 | /* Event Drag-n-Drop | ||
7148 | ------------------------------------------------------------------------------------------------------------------*/ | ||
7149 | |||
7150 | |||
7151 | // Computes if the given event is allowed to be dragged by the user | ||
7152 | isEventDraggable: function(event) { | ||
7153 | var source = event.source || {}; | ||
7154 | |||
7155 | return firstDefined( | ||
7156 | event.startEditable, | ||
7157 | source.startEditable, | ||
7158 | this.opt('eventStartEditable'), | ||
7159 | event.editable, | ||
7160 | source.editable, | ||
7161 | this.opt('editable') | ||
7162 | ); | ||
7163 | }, | ||
7164 | |||
7165 | |||
7166 | // Must be called when an event in the view is dropped onto new location. | ||
7167 | // `dropLocation` is an object that contains the new start/end/allDay values for the event. | ||
7168 | reportEventDrop: function(event, dropLocation, largeUnit, el, ev) { | ||
7169 | var calendar = this.calendar; | ||
7170 | var mutateResult = calendar.mutateEvent(event, dropLocation, largeUnit); | ||
7171 | var undoFunc = function() { | ||
7172 | mutateResult.undo(); | ||
7173 | calendar.reportEventChange(); | ||
7174 | }; | ||
7175 | |||
7176 | this.triggerEventDrop(event, mutateResult.dateDelta, undoFunc, el, ev); | ||
7177 | calendar.reportEventChange(); // will rerender events | ||
7178 | }, | ||
7179 | |||
7180 | |||
7181 | // Triggers event-drop handlers that have subscribed via the API | ||
7182 | triggerEventDrop: function(event, dateDelta, undoFunc, el, ev) { | ||
7183 | this.trigger('eventDrop', el[0], event, dateDelta, undoFunc, ev, {}); // {} = jqui dummy | ||
7184 | }, | ||
7185 | |||
7186 | |||
7187 | /* External Element Drag-n-Drop | ||
7188 | ------------------------------------------------------------------------------------------------------------------*/ | ||
7189 | |||
7190 | |||
7191 | // Must be called when an external element, via jQuery UI, has been dropped onto the calendar. | ||
7192 | // `meta` is the parsed data that has been embedded into the dragging event. | ||
7193 | // `dropLocation` is an object that contains the new start/end/allDay values for the event. | ||
7194 | reportExternalDrop: function(meta, dropLocation, el, ev, ui) { | ||
7195 | var eventProps = meta.eventProps; | ||
7196 | var eventInput; | ||
7197 | var event; | ||
7198 | |||
7199 | // Try to build an event object and render it. TODO: decouple the two | ||
7200 | if (eventProps) { | ||
7201 | eventInput = $.extend({}, eventProps, dropLocation); | ||
7202 | event = this.calendar.renderEvent(eventInput, meta.stick)[0]; // renderEvent returns an array | ||
7203 | } | ||
7204 | |||
7205 | this.triggerExternalDrop(event, dropLocation, el, ev, ui); | ||
7206 | }, | ||
7207 | |||
7208 | |||
7209 | // Triggers external-drop handlers that have subscribed via the API | ||
7210 | triggerExternalDrop: function(event, dropLocation, el, ev, ui) { | ||
7211 | |||
7212 | // trigger 'drop' regardless of whether element represents an event | ||
7213 | this.trigger('drop', el[0], dropLocation.start, ev, ui); | ||
7214 | |||
7215 | if (event) { | ||
7216 | this.trigger('eventReceive', null, event); // signal an external event landed | ||
7217 | } | ||
7218 | }, | ||
7219 | |||
7220 | |||
7221 | /* Drag-n-Drop Rendering (for both events and external elements) | ||
7222 | ------------------------------------------------------------------------------------------------------------------*/ | ||
7223 | |||
7224 | |||
7225 | // Renders a visual indication of a event or external-element drag over the given drop zone. | ||
7226 | // If an external-element, seg will be `null` | ||
7227 | renderDrag: function(dropLocation, seg) { | ||
7228 | // subclasses must implement | ||
7229 | }, | ||
7230 | |||
7231 | |||
7232 | // Unrenders a visual indication of an event or external-element being dragged. | ||
7233 | destroyDrag: function() { | ||
7234 | // subclasses must implement | ||
7235 | }, | ||
7236 | |||
7237 | |||
7238 | /* Event Resizing | ||
7239 | ------------------------------------------------------------------------------------------------------------------*/ | ||
7240 | |||
7241 | |||
7242 | // Computes if the given event is allowed to be resized from its starting edge | ||
7243 | isEventResizableFromStart: function(event) { | ||
7244 | return this.opt('eventResizableFromStart') && this.isEventResizable(event); | ||
7245 | }, | ||
7246 | |||
7247 | |||
7248 | // Computes if the given event is allowed to be resized from its ending edge | ||
7249 | isEventResizableFromEnd: function(event) { | ||
7250 | return this.isEventResizable(event); | ||
7251 | }, | ||
7252 | |||
7253 | |||
7254 | // Computes if the given event is allowed to be resized by the user at all | ||
7255 | isEventResizable: function(event) { | ||
7256 | var source = event.source || {}; | ||
7257 | |||
7258 | return firstDefined( | ||
7259 | event.durationEditable, | ||
7260 | source.durationEditable, | ||
7261 | this.opt('eventDurationEditable'), | ||
7262 | event.editable, | ||
7263 | source.editable, | ||
7264 | this.opt('editable') | ||
7265 | ); | ||
7266 | }, | ||
7267 | |||
7268 | |||
7269 | // Must be called when an event in the view has been resized to a new length | ||
7270 | reportEventResize: function(event, resizeLocation, largeUnit, el, ev) { | ||
7271 | var calendar = this.calendar; | ||
7272 | var mutateResult = calendar.mutateEvent(event, resizeLocation, largeUnit); | ||
7273 | var undoFunc = function() { | ||
7274 | mutateResult.undo(); | ||
7275 | calendar.reportEventChange(); | ||
7276 | }; | ||
7277 | |||
7278 | this.triggerEventResize(event, mutateResult.durationDelta, undoFunc, el, ev); | ||
7279 | calendar.reportEventChange(); // will rerender events | ||
7280 | }, | ||
7281 | |||
7282 | |||
7283 | // Triggers event-resize handlers that have subscribed via the API | ||
7284 | triggerEventResize: function(event, durationDelta, undoFunc, el, ev) { | ||
7285 | this.trigger('eventResize', el[0], event, durationDelta, undoFunc, ev, {}); // {} = jqui dummy | ||
7286 | }, | ||
7287 | |||
7288 | |||
7289 | /* Selection | ||
7290 | ------------------------------------------------------------------------------------------------------------------*/ | ||
7291 | |||
7292 | |||
7293 | // Selects a date range on the view. `start` and `end` are both Moments. | ||
7294 | // `ev` is the native mouse event that begin the interaction. | ||
7295 | select: function(range, ev) { | ||
7296 | this.unselect(ev); | ||
7297 | this.renderSelection(range); | ||
7298 | this.reportSelection(range, ev); | ||
7299 | }, | ||
7300 | |||
7301 | |||
7302 | // Renders a visual indication of the selection | ||
7303 | renderSelection: function(range) { | ||
7304 | // subclasses should implement | ||
7305 | }, | ||
7306 | |||
7307 | |||
7308 | // Called when a new selection is made. Updates internal state and triggers handlers. | ||
7309 | reportSelection: function(range, ev) { | ||
7310 | this.isSelected = true; | ||
7311 | this.trigger('select', null, range.start, range.end, ev); | ||
7312 | }, | ||
7313 | |||
7314 | |||
7315 | // Undoes a selection. updates in the internal state and triggers handlers. | ||
7316 | // `ev` is the native mouse event that began the interaction. | ||
7317 | unselect: function(ev) { | ||
7318 | if (this.isSelected) { | ||
7319 | this.isSelected = false; | ||
7320 | this.destroySelection(); | ||
7321 | this.trigger('unselect', null, ev); | ||
7322 | } | ||
7323 | }, | ||
7324 | |||
7325 | |||
7326 | // Unrenders a visual indication of selection | ||
7327 | destroySelection: function() { | ||
7328 | // subclasses should implement | ||
7329 | }, | ||
7330 | |||
7331 | |||
7332 | // Handler for unselecting when the user clicks something and the 'unselectAuto' setting is on | ||
7333 | documentMousedown: function(ev) { | ||
7334 | var ignore; | ||
7335 | |||
7336 | // is there a selection, and has the user made a proper left click? | ||
7337 | if (this.isSelected && this.opt('unselectAuto') && isPrimaryMouseButton(ev)) { | ||
7338 | |||
7339 | // only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element | ||
7340 | ignore = this.opt('unselectCancel'); | ||
7341 | if (!ignore || !$(ev.target).closest(ignore).length) { | ||
7342 | this.unselect(ev); | ||
7343 | } | ||
7344 | } | ||
7345 | }, | ||
7346 | |||
7347 | |||
7348 | /* Date Utils | ||
7349 | ------------------------------------------------------------------------------------------------------------------*/ | ||
7350 | |||
7351 | |||
7352 | // Initializes internal variables related to calculating hidden days-of-week | ||
7353 | initHiddenDays: function() { | ||
7354 | var hiddenDays = this.opt('hiddenDays') || []; // array of day-of-week indices that are hidden | ||
7355 | var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) | ||
7356 | var dayCnt = 0; | ||
7357 | var i; | ||
7358 | |||
7359 | if (this.opt('weekends') === false) { | ||
7360 | hiddenDays.push(0, 6); // 0=sunday, 6=saturday | ||
7361 | } | ||
7362 | |||
7363 | for (i = 0; i < 7; i++) { | ||
7364 | if ( | ||
7365 | !(isHiddenDayHash[i] = $.inArray(i, hiddenDays) !== -1) | ||
7366 | ) { | ||
7367 | dayCnt++; | ||
7368 | } | ||
7369 | } | ||
7370 | |||
7371 | if (!dayCnt) { | ||
7372 | throw 'invalid hiddenDays'; // all days were hidden? bad. | ||
7373 | } | ||
7374 | |||
7375 | this.isHiddenDayHash = isHiddenDayHash; | ||
7376 | }, | ||
7377 | |||
7378 | |||
7379 | // Is the current day hidden? | ||
7380 | // `day` is a day-of-week index (0-6), or a Moment | ||
7381 | isHiddenDay: function(day) { | ||
7382 | if (moment.isMoment(day)) { | ||
7383 | day = day.day(); | ||
7384 | } | ||
7385 | return this.isHiddenDayHash[day]; | ||
7386 | }, | ||
7387 | |||
7388 | |||
7389 | // Incrementing the current day until it is no longer a hidden day, returning a copy. | ||
7390 | // If the initial value of `date` is not a hidden day, don't do anything. | ||
7391 | // Pass `isExclusive` as `true` if you are dealing with an end date. | ||
7392 | // `inc` defaults to `1` (increment one day forward each time) | ||
7393 | skipHiddenDays: function(date, inc, isExclusive) { | ||
7394 | var out = date.clone(); | ||
7395 | inc = inc || 1; | ||
7396 | while ( | ||
7397 | this.isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7] | ||
7398 | ) { | ||
7399 | out.add(inc, 'days'); | ||
7400 | } | ||
7401 | return out; | ||
7402 | }, | ||
7403 | |||
7404 | |||
7405 | // Returns the date range of the full days the given range visually appears to occupy. | ||
7406 | // Returns a new range object. | ||
7407 | computeDayRange: function(range) { | ||
7408 | var startDay = range.start.clone().stripTime(); // the beginning of the day the range starts | ||
7409 | var end = range.end; | ||
7410 | var endDay = null; | ||
7411 | var endTimeMS; | ||
7412 | |||
7413 | if (end) { | ||
7414 | endDay = end.clone().stripTime(); // the beginning of the day the range exclusively ends | ||
7415 | endTimeMS = +end.time(); // # of milliseconds into `endDay` | ||
7416 | |||
7417 | // If the end time is actually inclusively part of the next day and is equal to or | ||
7418 | // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`. | ||
7419 | // Otherwise, leaving it as inclusive will cause it to exclude `endDay`. | ||
7420 | if (endTimeMS && endTimeMS >= this.nextDayThreshold) { | ||
7421 | endDay.add(1, 'days'); | ||
7422 | } | ||
7423 | } | ||
7424 | |||
7425 | // If no end was specified, or if it is within `startDay` but not past nextDayThreshold, | ||
7426 | // assign the default duration of one day. | ||
7427 | if (!end || endDay <= startDay) { | ||
7428 | endDay = startDay.clone().add(1, 'days'); | ||
7429 | } | ||
7430 | |||
7431 | return { start: startDay, end: endDay }; | ||
7432 | }, | ||
7433 | |||
7434 | |||
7435 | // Does the given event visually appear to occupy more than one day? | ||
7436 | isMultiDayEvent: function(event) { | ||
7437 | var range = this.computeDayRange(event); // event is range-ish | ||
7438 | |||
7439 | return range.end.diff(range.start, 'days') > 1; | ||
7440 | } | ||
7441 | |||
7442 | }); | ||
7443 | |||
7444 | ;; | ||
7445 | |||
7446 | var Calendar = fc.Calendar = fc.CalendarBase = Class.extend({ | ||
7447 | |||
7448 | dirDefaults: null, // option defaults related to LTR or RTL | ||
7449 | langDefaults: null, // option defaults related to current locale | ||
7450 | overrides: null, // option overrides given to the fullCalendar constructor | ||
7451 | options: null, // all defaults combined with overrides | ||
7452 | viewSpecCache: null, // cache of view definitions | ||
7453 | view: null, // current View object | ||
7454 | header: null, | ||
7455 | |||
7456 | |||
7457 | // a lot of this class' OOP logic is scoped within this constructor function, | ||
7458 | // but in the future, write individual methods on the prototype. | ||
7459 | constructor: Calendar_constructor, | ||
7460 | |||
7461 | |||
7462 | // Initializes `this.options` and other important options-related objects | ||
7463 | initOptions: function(overrides) { | ||
7464 | var lang, langDefaults; | ||
7465 | var isRTL, dirDefaults; | ||
7466 | |||
7467 | // converts legacy options into non-legacy ones. | ||
7468 | // in the future, when this is removed, don't use `overrides` reference. make a copy. | ||
7469 | overrides = massageOverrides(overrides); | ||
7470 | |||
7471 | lang = overrides.lang; | ||
7472 | langDefaults = langOptionHash[lang]; | ||
7473 | if (!langDefaults) { | ||
7474 | lang = Calendar.defaults.lang; | ||
7475 | langDefaults = langOptionHash[lang] || {}; | ||
7476 | } | ||
7477 | |||
7478 | isRTL = firstDefined( | ||
7479 | overrides.isRTL, | ||
7480 | langDefaults.isRTL, | ||
7481 | Calendar.defaults.isRTL | ||
7482 | ); | ||
7483 | dirDefaults = isRTL ? Calendar.rtlDefaults : {}; | ||
7484 | |||
7485 | this.dirDefaults = dirDefaults; | ||
7486 | this.langDefaults = langDefaults; | ||
7487 | this.overrides = overrides; | ||
7488 | this.options = mergeOptions( // merge defaults and overrides. lowest to highest precedence | ||
7489 | Calendar.defaults, // global defaults | ||
7490 | dirDefaults, | ||
7491 | langDefaults, | ||
7492 | overrides | ||
7493 | ); | ||
7494 | populateInstanceComputableOptions(this.options); | ||
7495 | |||
7496 | this.viewSpecCache = {}; // somewhat unrelated | ||
7497 | }, | ||
7498 | |||
7499 | |||
7500 | // Gets information about how to create a view. Will use a cache. | ||
7501 | getViewSpec: function(viewType) { | ||
7502 | var cache = this.viewSpecCache; | ||
7503 | |||
7504 | return cache[viewType] || (cache[viewType] = this.buildViewSpec(viewType)); | ||
7505 | }, | ||
7506 | |||
7507 | |||
7508 | // Given a duration singular unit, like "week" or "day", finds a matching view spec. | ||
7509 | // Preference is given to views that have corresponding buttons. | ||
7510 | getUnitViewSpec: function(unit) { | ||
7511 | var viewTypes; | ||
7512 | var i; | ||
7513 | var spec; | ||
7514 | |||
7515 | if ($.inArray(unit, intervalUnits) != -1) { | ||
7516 | |||
7517 | // put views that have buttons first. there will be duplicates, but oh well | ||
7518 | viewTypes = this.header.getViewsWithButtons(); | ||
7519 | $.each(fc.views, function(viewType) { // all views | ||
7520 | viewTypes.push(viewType); | ||
7521 | }); | ||
7522 | |||
7523 | for (i = 0; i < viewTypes.length; i++) { | ||
7524 | spec = this.getViewSpec(viewTypes[i]); | ||
7525 | if (spec) { | ||
7526 | if (spec.singleUnit == unit) { | ||
7527 | return spec; | ||
7528 | } | ||
7529 | } | ||
7530 | } | ||
7531 | } | ||
7532 | }, | ||
7533 | |||
7534 | |||
7535 | // Builds an object with information on how to create a given view | ||
7536 | buildViewSpec: function(requestedViewType) { | ||
7537 | var viewOverrides = this.overrides.views || {}; | ||
7538 | var defaultsChain = []; // for the view. lowest to highest priority | ||
7539 | var overridesChain = []; // for the view. lowest to highest priority | ||
7540 | var viewType = requestedViewType; | ||
7541 | var viewClass; | ||
7542 | var defaults; // for the view | ||
7543 | var overrides; // for the view | ||
7544 | var duration; | ||
7545 | var unit; | ||
7546 | var spec; | ||
7547 | |||
7548 | // iterate from the specific view definition to a more general one until we hit an actual View class | ||
7549 | while (viewType && !viewClass) { | ||
7550 | defaults = fcViews[viewType] || {}; | ||
7551 | overrides = viewOverrides[viewType] || {}; | ||
7552 | duration = duration || overrides.duration || defaults.duration; | ||
7553 | viewType = overrides.type || defaults.type; // for next iteration | ||
7554 | |||
7555 | if (typeof defaults === 'function') { // a class | ||
7556 | viewClass = defaults; | ||
7557 | defaultsChain.unshift(viewClass.defaults || {}); | ||
7558 | } | ||
7559 | else { // an options object | ||
7560 | defaultsChain.unshift(defaults); | ||
7561 | } | ||
7562 | overridesChain.unshift(overrides); | ||
7563 | } | ||
7564 | |||
7565 | if (viewClass) { | ||
7566 | spec = { 'class': viewClass, type: requestedViewType }; | ||
7567 | |||
7568 | if (duration) { | ||
7569 | duration = moment.duration(duration); | ||
7570 | if (!duration.valueOf()) { // invalid? | ||
7571 | duration = null; | ||
7572 | } | ||
7573 | } | ||
7574 | if (duration) { | ||
7575 | spec.duration = duration; | ||
7576 | unit = computeIntervalUnit(duration); | ||
7577 | |||
7578 | // view is a single-unit duration, like "week" or "day" | ||
7579 | // incorporate options for this. lowest priority | ||
7580 | if (duration.as(unit) === 1) { | ||
7581 | spec.singleUnit = unit; | ||
7582 | overridesChain.unshift(viewOverrides[unit] || {}); | ||
7583 | } | ||
7584 | } | ||
7585 | |||
7586 | // collapse into single objects | ||
7587 | spec.defaults = mergeOptions.apply(null, defaultsChain); | ||
7588 | spec.overrides = mergeOptions.apply(null, overridesChain); | ||
7589 | |||
7590 | this.buildViewSpecOptions(spec); | ||
7591 | this.buildViewSpecButtonText(spec, requestedViewType); | ||
7592 | |||
7593 | return spec; | ||
7594 | } | ||
7595 | }, | ||
7596 | |||
7597 | |||
7598 | // Builds and assigns a view spec's options object from its already-assigned defaults and overrides | ||
7599 | buildViewSpecOptions: function(spec) { | ||
7600 | spec.options = mergeOptions( // lowest to highest priority | ||
7601 | Calendar.defaults, // global defaults | ||
7602 | spec.defaults, // view's defaults (from ViewSubclass.defaults) | ||
7603 | this.dirDefaults, | ||
7604 | this.langDefaults, // locale and dir take precedence over view's defaults! | ||
7605 | this.overrides, // calendar's overrides (options given to constructor) | ||
7606 | spec.overrides // view's overrides (view-specific options) | ||
7607 | ); | ||
7608 | populateInstanceComputableOptions(spec.options); | ||
7609 | }, | ||
7610 | |||
7611 | |||
7612 | // Computes and assigns a view spec's buttonText-related options | ||
7613 | buildViewSpecButtonText: function(spec, requestedViewType) { | ||
7614 | |||
7615 | // given an options object with a possible `buttonText` hash, lookup the buttonText for the | ||
7616 | // requested view, falling back to a generic unit entry like "week" or "day" | ||
7617 | function queryButtonText(options) { | ||
7618 | var buttonText = options.buttonText || {}; | ||
7619 | return buttonText[requestedViewType] || | ||
7620 | (spec.singleUnit ? buttonText[spec.singleUnit] : null); | ||
7621 | } | ||
7622 | |||
7623 | // highest to lowest priority | ||
7624 | spec.buttonTextOverride = | ||
7625 | queryButtonText(this.overrides) || // constructor-specified buttonText lookup hash takes precedence | ||
7626 | spec.overrides.buttonText; // `buttonText` for view-specific options is a string | ||
7627 | |||
7628 | // highest to lowest priority. mirrors buildViewSpecOptions | ||
7629 | spec.buttonTextDefault = | ||
7630 | queryButtonText(this.langDefaults) || | ||
7631 | queryButtonText(this.dirDefaults) || | ||
7632 | spec.defaults.buttonText || // a single string. from ViewSubclass.defaults | ||
7633 | queryButtonText(Calendar.defaults) || | ||
7634 | (spec.duration ? this.humanizeDuration(spec.duration) : null) || // like "3 days" | ||
7635 | requestedViewType; // fall back to given view name | ||
7636 | }, | ||
7637 | |||
7638 | |||
7639 | // Given a view name for a custom view or a standard view, creates a ready-to-go View object | ||
7640 | instantiateView: function(viewType) { | ||
7641 | var spec = this.getViewSpec(viewType); | ||
7642 | |||
7643 | return new spec['class'](this, viewType, spec.options, spec.duration); | ||
7644 | }, | ||
7645 | |||
7646 | |||
7647 | // Returns a boolean about whether the view is okay to instantiate at some point | ||
7648 | isValidViewType: function(viewType) { | ||
7649 | return Boolean(this.getViewSpec(viewType)); | ||
7650 | } | ||
7651 | |||
7652 | }); | ||
7653 | |||
7654 | |||
7655 | function Calendar_constructor(element, overrides) { | ||
7656 | var t = this; | ||
7657 | |||
7658 | |||
7659 | t.initOptions(overrides || {}); | ||
7660 | var options = this.options; | ||
7661 | |||
7662 | |||
7663 | // Exports | ||
7664 | // ----------------------------------------------------------------------------------- | ||
7665 | |||
7666 | t.render = render; | ||
7667 | t.destroy = destroy; | ||
7668 | t.refetchEvents = refetchEvents; | ||
7669 | t.reportEvents = reportEvents; | ||
7670 | t.reportEventChange = reportEventChange; | ||
7671 | t.rerenderEvents = renderEvents; // `renderEvents` serves as a rerender. an API method | ||
7672 | t.changeView = renderView; // `renderView` will switch to another view | ||
7673 | t.select = select; | ||
7674 | t.unselect = unselect; | ||
7675 | t.prev = prev; | ||
7676 | t.next = next; | ||
7677 | t.prevYear = prevYear; | ||
7678 | t.nextYear = nextYear; | ||
7679 | t.today = today; | ||
7680 | t.gotoDate = gotoDate; | ||
7681 | t.incrementDate = incrementDate; | ||
7682 | t.zoomTo = zoomTo; | ||
7683 | t.getDate = getDate; | ||
7684 | t.getCalendar = getCalendar; | ||
7685 | t.getView = getView; | ||
7686 | t.option = option; | ||
7687 | t.trigger = trigger; | ||
7688 | |||
7689 | |||
7690 | |||
7691 | // Language-data Internals | ||
7692 | // ----------------------------------------------------------------------------------- | ||
7693 | // Apply overrides to the current language's data | ||
7694 | |||
7695 | |||
7696 | var localeData = createObject( // make a cheap copy | ||
7697 | getMomentLocaleData(options.lang) // will fall back to en | ||
7698 | ); | ||
7699 | |||
7700 | if (options.monthNames) { | ||
7701 | localeData._months = options.monthNames; | ||
7702 | } | ||
7703 | if (options.monthNamesShort) { | ||
7704 | localeData._monthsShort = options.monthNamesShort; | ||
7705 | } | ||
7706 | if (options.dayNames) { | ||
7707 | localeData._weekdays = options.dayNames; | ||
7708 | } | ||
7709 | if (options.dayNamesShort) { | ||
7710 | localeData._weekdaysShort = options.dayNamesShort; | ||
7711 | } | ||
7712 | if (options.firstDay != null) { | ||
7713 | var _week = createObject(localeData._week); // _week: { dow: # } | ||
7714 | _week.dow = options.firstDay; | ||
7715 | localeData._week = _week; | ||
7716 | } | ||
7717 | |||
7718 | // assign a normalized value, to be used by our .week() moment extension | ||
7719 | localeData._fullCalendar_weekCalc = (function(weekCalc) { | ||
7720 | if (typeof weekCalc === 'function') { | ||
7721 | return weekCalc; | ||
7722 | } | ||
7723 | else if (weekCalc === 'local') { | ||
7724 | return weekCalc; | ||
7725 | } | ||
7726 | else if (weekCalc === 'iso' || weekCalc === 'ISO') { | ||
7727 | return 'ISO'; | ||
7728 | } | ||
7729 | })(options.weekNumberCalculation); | ||
7730 | |||
7731 | |||
7732 | |||
7733 | // Calendar-specific Date Utilities | ||
7734 | // ----------------------------------------------------------------------------------- | ||
7735 | |||
7736 | |||
7737 | t.defaultAllDayEventDuration = moment.duration(options.defaultAllDayEventDuration); | ||
7738 | t.defaultTimedEventDuration = moment.duration(options.defaultTimedEventDuration); | ||
7739 | |||
7740 | |||
7741 | // Builds a moment using the settings of the current calendar: timezone and language. | ||
7742 | // Accepts anything the vanilla moment() constructor accepts. | ||
7743 | t.moment = function() { | ||
7744 | var mom; | ||
7745 | |||
7746 | if (options.timezone === 'local') { | ||
7747 | mom = fc.moment.apply(null, arguments); | ||
7748 | |||
7749 | // Force the moment to be local, because fc.moment doesn't guarantee it. | ||
7750 | if (mom.hasTime()) { // don't give ambiguously-timed moments a local zone | ||
7751 | mom.local(); | ||
7752 | } | ||
7753 | } | ||
7754 | else if (options.timezone === 'UTC') { | ||
7755 | mom = fc.moment.utc.apply(null, arguments); // process as UTC | ||
7756 | } | ||
7757 | else { | ||
7758 | mom = fc.moment.parseZone.apply(null, arguments); // let the input decide the zone | ||
7759 | } | ||
7760 | |||
7761 | if ('_locale' in mom) { // moment 2.8 and above | ||
7762 | mom._locale = localeData; | ||
7763 | } | ||
7764 | else { // pre-moment-2.8 | ||
7765 | mom._lang = localeData; | ||
7766 | } | ||
7767 | |||
7768 | return mom; | ||
7769 | }; | ||
7770 | |||
7771 | |||
7772 | // Returns a boolean about whether or not the calendar knows how to calculate | ||
7773 | // the timezone offset of arbitrary dates in the current timezone. | ||
7774 | t.getIsAmbigTimezone = function() { | ||
7775 | return options.timezone !== 'local' && options.timezone !== 'UTC'; | ||
7776 | }; | ||
7777 | |||
7778 | |||
7779 | // Returns a copy of the given date in the current timezone of it is ambiguously zoned. | ||
7780 | // This will also give the date an unambiguous time. | ||
7781 | t.rezoneDate = function(date) { | ||
7782 | return t.moment(date.toArray()); | ||
7783 | }; | ||
7784 | |||
7785 | |||
7786 | // Returns a moment for the current date, as defined by the client's computer, | ||
7787 | // or overridden by the `now` option. | ||
7788 | t.getNow = function() { | ||
7789 | var now = options.now; | ||
7790 | if (typeof now === 'function') { | ||
7791 | now = now(); | ||
7792 | } | ||
7793 | return t.moment(now); | ||
7794 | }; | ||
7795 | |||
7796 | |||
7797 | // Get an event's normalized end date. If not present, calculate it from the defaults. | ||
7798 | t.getEventEnd = function(event) { | ||
7799 | if (event.end) { | ||
7800 | return event.end.clone(); | ||
7801 | } | ||
7802 | else { | ||
7803 | return t.getDefaultEventEnd(event.allDay, event.start); | ||
7804 | } | ||
7805 | }; | ||
7806 | |||
7807 | |||
7808 | // Given an event's allDay status and start date, return swhat its fallback end date should be. | ||
7809 | t.getDefaultEventEnd = function(allDay, start) { // TODO: rename to computeDefaultEventEnd | ||
7810 | var end = start.clone(); | ||
7811 | |||
7812 | if (allDay) { | ||
7813 | end.stripTime().add(t.defaultAllDayEventDuration); | ||
7814 | } | ||
7815 | else { | ||
7816 | end.add(t.defaultTimedEventDuration); | ||
7817 | } | ||
7818 | |||
7819 | if (t.getIsAmbigTimezone()) { | ||
7820 | end.stripZone(); // we don't know what the tzo should be | ||
7821 | } | ||
7822 | |||
7823 | return end; | ||
7824 | }; | ||
7825 | |||
7826 | |||
7827 | // Produces a human-readable string for the given duration. | ||
7828 | // Side-effect: changes the locale of the given duration. | ||
7829 | t.humanizeDuration = function(duration) { | ||
7830 | return (duration.locale || duration.lang).call(duration, options.lang) // works moment-pre-2.8 | ||
7831 | .humanize(); | ||
7832 | }; | ||
7833 | |||
7834 | |||
7835 | |||
7836 | // Imports | ||
7837 | // ----------------------------------------------------------------------------------- | ||
7838 | |||
7839 | |||
7840 | EventManager.call(t, options); | ||
7841 | var isFetchNeeded = t.isFetchNeeded; | ||
7842 | var fetchEvents = t.fetchEvents; | ||
7843 | |||
7844 | |||
7845 | |||
7846 | // Locals | ||
7847 | // ----------------------------------------------------------------------------------- | ||
7848 | |||
7849 | |||
7850 | var _element = element[0]; | ||
7851 | var header; | ||
7852 | var headerElement; | ||
7853 | var content; | ||
7854 | var tm; // for making theme classes | ||
7855 | var currentView; // NOTE: keep this in sync with this.view | ||
7856 | var viewsByType = {}; // holds all instantiated view instances, current or not | ||
7857 | var suggestedViewHeight; | ||
7858 | var windowResizeProxy; // wraps the windowResize function | ||
7859 | var ignoreWindowResize = 0; | ||
7860 | var date; | ||
7861 | var events = []; | ||
7862 | |||
7863 | |||
7864 | |||
7865 | // Main Rendering | ||
7866 | // ----------------------------------------------------------------------------------- | ||
7867 | |||
7868 | |||
7869 | if (options.defaultDate != null) { | ||
7870 | date = t.moment(options.defaultDate); | ||
7871 | } | ||
7872 | else { | ||
7873 | date = t.getNow(); | ||
7874 | } | ||
7875 | |||
7876 | |||
7877 | function render() { | ||
7878 | if (!content) { | ||
7879 | initialRender(); | ||
7880 | } | ||
7881 | else if (elementVisible()) { | ||
7882 | // mainly for the public API | ||
7883 | calcSize(); | ||
7884 | renderView(); | ||
7885 | } | ||
7886 | } | ||
7887 | |||
7888 | |||
7889 | function initialRender() { | ||
7890 | tm = options.theme ? 'ui' : 'fc'; | ||
7891 | element.addClass('fc'); | ||
7892 | |||
7893 | if (options.isRTL) { | ||
7894 | element.addClass('fc-rtl'); | ||
7895 | } | ||
7896 | else { | ||
7897 | element.addClass('fc-ltr'); | ||
7898 | } | ||
7899 | |||
7900 | if (options.theme) { | ||
7901 | element.addClass('ui-widget'); | ||
7902 | } | ||
7903 | else { | ||
7904 | element.addClass('fc-unthemed'); | ||
7905 | } | ||
7906 | |||
7907 | content = $("<div class='fc-view-container'/>").prependTo(element); | ||
7908 | |||
7909 | header = t.header = new Header(t, options); | ||
7910 | headerElement = header.render(); | ||
7911 | if (headerElement) { | ||
7912 | element.prepend(headerElement); | ||
7913 | } | ||
7914 | |||
7915 | renderView(options.defaultView); | ||
7916 | |||
7917 | if (options.handleWindowResize) { | ||
7918 | windowResizeProxy = debounce(windowResize, options.windowResizeDelay); // prevents rapid calls | ||
7919 | $(window).resize(windowResizeProxy); | ||
7920 | } | ||
7921 | } | ||
7922 | |||
7923 | |||
7924 | function destroy() { | ||
7925 | |||
7926 | if (currentView) { | ||
7927 | currentView.removeElement(); | ||
7928 | |||
7929 | // NOTE: don't null-out currentView/t.view in case API methods are called after destroy. | ||
7930 | // It is still the "current" view, just not rendered. | ||
7931 | } | ||
7932 | |||
7933 | header.destroy(); | ||
7934 | content.remove(); | ||
7935 | element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget'); | ||
7936 | |||
7937 | if (windowResizeProxy) { | ||
7938 | $(window).unbind('resize', windowResizeProxy); | ||
7939 | } | ||
7940 | } | ||
7941 | |||
7942 | |||
7943 | function elementVisible() { | ||
7944 | return element.is(':visible'); | ||
7945 | } | ||
7946 | |||
7947 | |||
7948 | |||
7949 | // View Rendering | ||
7950 | // ----------------------------------------------------------------------------------- | ||
7951 | |||
7952 | |||
7953 | // Renders a view because of a date change, view-type change, or for the first time. | ||
7954 | // If not given a viewType, keep the current view but render different dates. | ||
7955 | function renderView(viewType) { | ||
7956 | ignoreWindowResize++; | ||
7957 | |||
7958 | // if viewType is changing, destroy the old view | ||
7959 | if (currentView && viewType && currentView.type !== viewType) { | ||
7960 | header.deactivateButton(currentView.type); | ||
7961 | freezeContentHeight(); // prevent a scroll jump when view element is removed | ||
7962 | currentView.removeElement(); | ||
7963 | currentView = t.view = null; | ||
7964 | } | ||
7965 | |||
7966 | // if viewType changed, or the view was never created, create a fresh view | ||
7967 | if (!currentView && viewType) { | ||
7968 | currentView = t.view = | ||
7969 | viewsByType[viewType] || | ||
7970 | (viewsByType[viewType] = t.instantiateView(viewType)); | ||
7971 | |||
7972 | currentView.setElement( | ||
7973 | $("<div class='fc-view fc-" + viewType + "-view' />").appendTo(content) | ||
7974 | ); | ||
7975 | header.activateButton(viewType); | ||
7976 | } | ||
7977 | |||
7978 | if (currentView) { | ||
7979 | |||
7980 | // in case the view should render a period of time that is completely hidden | ||
7981 | date = currentView.massageCurrentDate(date); | ||
7982 | |||
7983 | // render or rerender the view | ||
7984 | if ( | ||
7985 | !currentView.isDisplayed || | ||
7986 | !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change | ||
7987 | ) { | ||
7988 | if (elementVisible()) { | ||
7989 | |||
7990 | freezeContentHeight(); | ||
7991 | currentView.display(date); | ||
7992 | unfreezeContentHeight(); | ||
7993 | |||
7994 | // need to do this after View::render, so dates are calculated | ||
7995 | updateHeaderTitle(); | ||
7996 | updateTodayButton(); | ||
7997 | |||
7998 | getAndRenderEvents(); | ||
7999 | } | ||
8000 | } | ||
8001 | } | ||
8002 | |||
8003 | unfreezeContentHeight(); // undo any lone freezeContentHeight calls | ||
8004 | ignoreWindowResize--; | ||
8005 | } | ||
8006 | |||
8007 | |||
8008 | |||
8009 | // Resizing | ||
8010 | // ----------------------------------------------------------------------------------- | ||
8011 | |||
8012 | |||
8013 | t.getSuggestedViewHeight = function() { | ||
8014 | if (suggestedViewHeight === undefined) { | ||
8015 | calcSize(); | ||
8016 | } | ||
8017 | return suggestedViewHeight; | ||
8018 | }; | ||
8019 | |||
8020 | |||
8021 | t.isHeightAuto = function() { | ||
8022 | return options.contentHeight === 'auto' || options.height === 'auto'; | ||
8023 | }; | ||
8024 | |||
8025 | |||
8026 | function updateSize(shouldRecalc) { | ||
8027 | if (elementVisible()) { | ||
8028 | |||
8029 | if (shouldRecalc) { | ||
8030 | _calcSize(); | ||
8031 | } | ||
8032 | |||
8033 | ignoreWindowResize++; | ||
8034 | currentView.updateSize(true); // isResize=true. will poll getSuggestedViewHeight() and isHeightAuto() | ||
8035 | ignoreWindowResize--; | ||
8036 | |||
8037 | return true; // signal success | ||
8038 | } | ||
8039 | } | ||
8040 | |||
8041 | |||
8042 | function calcSize() { | ||
8043 | if (elementVisible()) { | ||
8044 | _calcSize(); | ||
8045 | } | ||
8046 | } | ||
8047 | |||
8048 | |||
8049 | function _calcSize() { // assumes elementVisible | ||
8050 | if (typeof options.contentHeight === 'number') { // exists and not 'auto' | ||
8051 | suggestedViewHeight = options.contentHeight; | ||
8052 | } | ||
8053 | else if (typeof options.height === 'number') { // exists and not 'auto' | ||
8054 | suggestedViewHeight = options.height - (headerElement ? headerElement.outerHeight(true) : 0); | ||
8055 | } | ||
8056 | else { | ||
8057 | suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5)); | ||
8058 | } | ||
8059 | } | ||
8060 | |||
8061 | |||
8062 | function windowResize(ev) { | ||
8063 | if ( | ||
8064 | !ignoreWindowResize && | ||
8065 | ev.target === window && // so we don't process jqui "resize" events that have bubbled up | ||
8066 | currentView.start // view has already been rendered | ||
8067 | ) { | ||
8068 | if (updateSize(true)) { | ||
8069 | currentView.trigger('windowResize', _element); | ||
8070 | } | ||
8071 | } | ||
8072 | } | ||
8073 | |||
8074 | |||
8075 | |||
8076 | /* Event Fetching/Rendering | ||
8077 | -----------------------------------------------------------------------------*/ | ||
8078 | // TODO: going forward, most of this stuff should be directly handled by the view | ||
8079 | |||
8080 | |||
8081 | function refetchEvents() { // can be called as an API method | ||
8082 | destroyEvents(); // so that events are cleared before user starts waiting for AJAX | ||
8083 | fetchAndRenderEvents(); | ||
8084 | } | ||
8085 | |||
8086 | |||
8087 | function renderEvents() { // destroys old events if previously rendered | ||
8088 | if (elementVisible()) { | ||
8089 | freezeContentHeight(); | ||
8090 | currentView.displayEvents(events); | ||
8091 | unfreezeContentHeight(); | ||
8092 | } | ||
8093 | } | ||
8094 | |||
8095 | |||
8096 | function destroyEvents() { | ||
8097 | freezeContentHeight(); | ||
8098 | currentView.clearEvents(); | ||
8099 | unfreezeContentHeight(); | ||
8100 | } | ||
8101 | |||
8102 | |||
8103 | function getAndRenderEvents() { | ||
8104 | if (!options.lazyFetching || isFetchNeeded(currentView.start, currentView.end)) { | ||
8105 | fetchAndRenderEvents(); | ||
8106 | } | ||
8107 | else { | ||
8108 | renderEvents(); | ||
8109 | } | ||
8110 | } | ||
8111 | |||
8112 | |||
8113 | function fetchAndRenderEvents() { | ||
8114 | fetchEvents(currentView.start, currentView.end); | ||
8115 | // ... will call reportEvents | ||
8116 | // ... which will call renderEvents | ||
8117 | } | ||
8118 | |||
8119 | |||
8120 | // called when event data arrives | ||
8121 | function reportEvents(_events) { | ||
8122 | events = _events; | ||
8123 | renderEvents(); | ||
8124 | } | ||
8125 | |||
8126 | |||
8127 | // called when a single event's data has been changed | ||
8128 | function reportEventChange() { | ||
8129 | renderEvents(); | ||
8130 | } | ||
8131 | |||
8132 | |||
8133 | |||
8134 | /* Header Updating | ||
8135 | -----------------------------------------------------------------------------*/ | ||
8136 | |||
8137 | |||
8138 | function updateHeaderTitle() { | ||
8139 | header.updateTitle(currentView.title); | ||
8140 | } | ||
8141 | |||
8142 | |||
8143 | function updateTodayButton() { | ||
8144 | var now = t.getNow(); | ||
8145 | if (now.isWithin(currentView.intervalStart, currentView.intervalEnd)) { | ||
8146 | header.disableButton('today'); | ||
8147 | } | ||
8148 | else { | ||
8149 | header.enableButton('today'); | ||
8150 | } | ||
8151 | } | ||
8152 | |||
8153 | |||
8154 | |||
8155 | /* Selection | ||
8156 | -----------------------------------------------------------------------------*/ | ||
8157 | |||
8158 | |||
8159 | function select(start, end) { | ||
8160 | |||
8161 | start = t.moment(start); | ||
8162 | if (end) { | ||
8163 | end = t.moment(end); | ||
8164 | } | ||
8165 | else if (start.hasTime()) { | ||
8166 | end = start.clone().add(t.defaultTimedEventDuration); | ||
8167 | } | ||
8168 | else { | ||
8169 | end = start.clone().add(t.defaultAllDayEventDuration); | ||
8170 | } | ||
8171 | |||
8172 | currentView.select({ start: start, end: end }); // accepts a range | ||
8173 | } | ||
8174 | |||
8175 | |||
8176 | function unselect() { // safe to be called before renderView | ||
8177 | if (currentView) { | ||
8178 | currentView.unselect(); | ||
8179 | } | ||
8180 | } | ||
8181 | |||
8182 | |||
8183 | |||
8184 | /* Date | ||
8185 | -----------------------------------------------------------------------------*/ | ||
8186 | |||
8187 | |||
8188 | function prev() { | ||
8189 | date = currentView.computePrevDate(date); | ||
8190 | renderView(); | ||
8191 | } | ||
8192 | |||
8193 | |||
8194 | function next() { | ||
8195 | date = currentView.computeNextDate(date); | ||
8196 | renderView(); | ||
8197 | } | ||
8198 | |||
8199 | |||
8200 | function prevYear() { | ||
8201 | date.add(-1, 'years'); | ||
8202 | renderView(); | ||
8203 | } | ||
8204 | |||
8205 | |||
8206 | function nextYear() { | ||
8207 | date.add(1, 'years'); | ||
8208 | renderView(); | ||
8209 | } | ||
8210 | |||
8211 | |||
8212 | function today() { | ||
8213 | date = t.getNow(); | ||
8214 | renderView(); | ||
8215 | } | ||
8216 | |||
8217 | |||
8218 | function gotoDate(dateInput) { | ||
8219 | date = t.moment(dateInput); | ||
8220 | renderView(); | ||
8221 | } | ||
8222 | |||
8223 | |||
8224 | function incrementDate(delta) { | ||
8225 | date.add(moment.duration(delta)); | ||
8226 | renderView(); | ||
8227 | } | ||
8228 | |||
8229 | |||
8230 | // Forces navigation to a view for the given date. | ||
8231 | // `viewType` can be a specific view name or a generic one like "week" or "day". | ||
8232 | function zoomTo(newDate, viewType) { | ||
8233 | var spec; | ||
8234 | |||
8235 | viewType = viewType || 'day'; // day is default zoom | ||
8236 | spec = t.getViewSpec(viewType) || t.getUnitViewSpec(viewType); | ||
8237 | |||
8238 | date = newDate; | ||
8239 | renderView(spec ? spec.type : null); | ||
8240 | } | ||
8241 | |||
8242 | |||
8243 | function getDate() { | ||
8244 | return date.clone(); | ||
8245 | } | ||
8246 | |||
8247 | |||
8248 | |||
8249 | /* Height "Freezing" | ||
8250 | -----------------------------------------------------------------------------*/ | ||
8251 | // TODO: move this into the view | ||
8252 | |||
8253 | |||
8254 | function freezeContentHeight() { | ||
8255 | content.css({ | ||
8256 | width: '100%', | ||
8257 | height: content.height(), | ||
8258 | overflow: 'hidden' | ||
8259 | }); | ||
8260 | } | ||
8261 | |||
8262 | |||
8263 | function unfreezeContentHeight() { | ||
8264 | content.css({ | ||
8265 | width: '', | ||
8266 | height: '', | ||
8267 | overflow: '' | ||
8268 | }); | ||
8269 | } | ||
8270 | |||
8271 | |||
8272 | |||
8273 | /* Misc | ||
8274 | -----------------------------------------------------------------------------*/ | ||
8275 | |||
8276 | |||
8277 | function getCalendar() { | ||
8278 | return t; | ||
8279 | } | ||
8280 | |||
8281 | |||
8282 | function getView() { | ||
8283 | return currentView; | ||
8284 | } | ||
8285 | |||
8286 | |||
8287 | function option(name, value) { | ||
8288 | if (value === undefined) { | ||
8289 | return options[name]; | ||
8290 | } | ||
8291 | if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') { | ||
8292 | options[name] = value; | ||
8293 | updateSize(true); // true = allow recalculation of height | ||
8294 | } | ||
8295 | } | ||
8296 | |||
8297 | |||
8298 | function trigger(name, thisObj) { | ||
8299 | if (options[name]) { | ||
8300 | return options[name].apply( | ||
8301 | thisObj || _element, | ||
8302 | Array.prototype.slice.call(arguments, 2) | ||
8303 | ); | ||
8304 | } | ||
8305 | } | ||
8306 | |||
8307 | } | ||
8308 | |||
8309 | ;; | ||
8310 | |||
8311 | Calendar.defaults = { | ||
8312 | |||
8313 | titleRangeSeparator: ' \u2014 ', // emphasized dash | ||
8314 | monthYearFormat: 'MMMM YYYY', // required for en. other languages rely on datepicker computable option | ||
8315 | |||
8316 | defaultTimedEventDuration: '02:00:00', | ||
8317 | defaultAllDayEventDuration: { days: 1 }, | ||
8318 | forceEventDuration: false, | ||
8319 | nextDayThreshold: '09:00:00', // 9am | ||
8320 | |||
8321 | // display | ||
8322 | defaultView: 'month', | ||
8323 | aspectRatio: 1.35, | ||
8324 | header: { | ||
8325 | left: 'title', | ||
8326 | center: '', | ||
8327 | right: 'today prev,next' | ||
8328 | }, | ||
8329 | weekends: true, | ||
8330 | weekNumbers: false, | ||
8331 | |||
8332 | weekNumberTitle: 'W', | ||
8333 | weekNumberCalculation: 'local', | ||
8334 | |||
8335 | //editable: false, | ||
8336 | |||
8337 | // event ajax | ||
8338 | lazyFetching: true, | ||
8339 | startParam: 'start', | ||
8340 | endParam: 'end', | ||
8341 | timezoneParam: 'timezone', | ||
8342 | |||
8343 | timezone: false, | ||
8344 | |||
8345 | //allDayDefault: undefined, | ||
8346 | |||
8347 | // locale | ||
8348 | isRTL: false, | ||
8349 | buttonText: { | ||
8350 | prev: "prev", | ||
8351 | next: "next", | ||
8352 | prevYear: "prev year", | ||
8353 | nextYear: "next year", | ||
8354 | year: 'year', // TODO: locale files need to specify this | ||
8355 | today: 'today', | ||
8356 | month: 'month', | ||
8357 | week: 'week', | ||
8358 | day: 'day' | ||
8359 | }, | ||
8360 | |||
8361 | buttonIcons: { | ||
8362 | prev: 'left-single-arrow', | ||
8363 | next: 'right-single-arrow', | ||
8364 | prevYear: 'left-double-arrow', | ||
8365 | nextYear: 'right-double-arrow' | ||
8366 | }, | ||
8367 | |||
8368 | // jquery-ui theming | ||
8369 | theme: false, | ||
8370 | themeButtonIcons: { | ||
8371 | prev: 'circle-triangle-w', | ||
8372 | next: 'circle-triangle-e', | ||
8373 | prevYear: 'seek-prev', | ||
8374 | nextYear: 'seek-next' | ||
8375 | }, | ||
8376 | |||
8377 | //eventResizableFromStart: false, | ||
8378 | dragOpacity: .75, | ||
8379 | dragRevertDuration: 500, | ||
8380 | dragScroll: true, | ||
8381 | |||
8382 | //selectable: false, | ||
8383 | unselectAuto: true, | ||
8384 | |||
8385 | dropAccept: '*', | ||
8386 | |||
8387 | eventLimit: false, | ||
8388 | eventLimitText: 'more', | ||
8389 | eventLimitClick: 'popover', | ||
8390 | dayPopoverFormat: 'LL', | ||
8391 | |||
8392 | handleWindowResize: true, | ||
8393 | windowResizeDelay: 200 // milliseconds before an updateSize happens | ||
8394 | |||
8395 | }; | ||
8396 | |||
8397 | |||
8398 | Calendar.englishDefaults = { // used by lang.js | ||
8399 | dayPopoverFormat: 'dddd, MMMM D' | ||
8400 | }; | ||
8401 | |||
8402 | |||
8403 | Calendar.rtlDefaults = { // right-to-left defaults | ||
8404 | header: { // TODO: smarter solution (first/center/last ?) | ||
8405 | left: 'next,prev today', | ||
8406 | center: '', | ||
8407 | right: 'title' | ||
8408 | }, | ||
8409 | buttonIcons: { | ||
8410 | prev: 'right-single-arrow', | ||
8411 | next: 'left-single-arrow', | ||
8412 | prevYear: 'right-double-arrow', | ||
8413 | nextYear: 'left-double-arrow' | ||
8414 | }, | ||
8415 | themeButtonIcons: { | ||
8416 | prev: 'circle-triangle-e', | ||
8417 | next: 'circle-triangle-w', | ||
8418 | nextYear: 'seek-prev', | ||
8419 | prevYear: 'seek-next' | ||
8420 | } | ||
8421 | }; | ||
8422 | |||
8423 | ;; | ||
8424 | |||
8425 | var langOptionHash = fc.langs = {}; // initialize and expose | ||
8426 | |||
8427 | |||
8428 | // TODO: document the structure and ordering of a FullCalendar lang file | ||
8429 | // TODO: rename everything "lang" to "locale", like what the moment project did | ||
8430 | |||
8431 | |||
8432 | // Initialize jQuery UI datepicker translations while using some of the translations | ||
8433 | // Will set this as the default language for datepicker. | ||
8434 | fc.datepickerLang = function(langCode, dpLangCode, dpOptions) { | ||
8435 | |||
8436 | // get the FullCalendar internal option hash for this language. create if necessary | ||
8437 | var fcOptions = langOptionHash[langCode] || (langOptionHash[langCode] = {}); | ||
8438 | |||
8439 | // transfer some simple options from datepicker to fc | ||
8440 | fcOptions.isRTL = dpOptions.isRTL; | ||
8441 | fcOptions.weekNumberTitle = dpOptions.weekHeader; | ||
8442 | |||
8443 | // compute some more complex options from datepicker | ||
8444 | $.each(dpComputableOptions, function(name, func) { | ||
8445 | fcOptions[name] = func(dpOptions); | ||
8446 | }); | ||
8447 | |||
8448 | // is jQuery UI Datepicker is on the page? | ||
8449 | if ($.datepicker) { | ||
8450 | |||
8451 | // Register the language data. | ||
8452 | // FullCalendar and MomentJS use language codes like "pt-br" but Datepicker | ||
8453 | // does it like "pt-BR" or if it doesn't have the language, maybe just "pt". | ||
8454 | // Make an alias so the language can be referenced either way. | ||
8455 | $.datepicker.regional[dpLangCode] = | ||
8456 | $.datepicker.regional[langCode] = // alias | ||
8457 | dpOptions; | ||
8458 | |||
8459 | // Alias 'en' to the default language data. Do this every time. | ||
8460 | $.datepicker.regional.en = $.datepicker.regional['']; | ||
8461 | |||
8462 | // Set as Datepicker's global defaults. | ||
8463 | $.datepicker.setDefaults(dpOptions); | ||
8464 | } | ||
8465 | }; | ||
8466 | |||
8467 | |||
8468 | // Sets FullCalendar-specific translations. Will set the language as the global default. | ||
8469 | fc.lang = function(langCode, newFcOptions) { | ||
8470 | var fcOptions; | ||
8471 | var momOptions; | ||
8472 | |||
8473 | // get the FullCalendar internal option hash for this language. create if necessary | ||
8474 | fcOptions = langOptionHash[langCode] || (langOptionHash[langCode] = {}); | ||
8475 | |||
8476 | // provided new options for this language? merge them in | ||
8477 | if (newFcOptions) { | ||
8478 | fcOptions = langOptionHash[langCode] = mergeOptions(fcOptions, newFcOptions); | ||
8479 | } | ||
8480 | |||
8481 | // compute language options that weren't defined. | ||
8482 | // always do this. newFcOptions can be undefined when initializing from i18n file, | ||
8483 | // so no way to tell if this is an initialization or a default-setting. | ||
8484 | momOptions = getMomentLocaleData(langCode); // will fall back to en | ||
8485 | $.each(momComputableOptions, function(name, func) { | ||
8486 | if (fcOptions[name] == null) { | ||
8487 | fcOptions[name] = func(momOptions, fcOptions); | ||
8488 | } | ||
8489 | }); | ||
8490 | |||
8491 | // set it as the default language for FullCalendar | ||
8492 | Calendar.defaults.lang = langCode; | ||
8493 | }; | ||
8494 | |||
8495 | |||
8496 | // NOTE: can't guarantee any of these computations will run because not every language has datepicker | ||
8497 | // configs, so make sure there are English fallbacks for these in the defaults file. | ||
8498 | var dpComputableOptions = { | ||
8499 | |||
8500 | buttonText: function(dpOptions) { | ||
8501 | return { | ||
8502 | // the translations sometimes wrongly contain HTML entities | ||
8503 | prev: stripHtmlEntities(dpOptions.prevText), | ||
8504 | next: stripHtmlEntities(dpOptions.nextText), | ||
8505 | today: stripHtmlEntities(dpOptions.currentText) | ||
8506 | }; | ||
8507 | }, | ||
8508 | |||
8509 | // Produces format strings like "MMMM YYYY" -> "September 2014" | ||
8510 | monthYearFormat: function(dpOptions) { | ||
8511 | return dpOptions.showMonthAfterYear ? | ||
8512 | 'YYYY[' + dpOptions.yearSuffix + '] MMMM' : | ||
8513 | 'MMMM YYYY[' + dpOptions.yearSuffix + ']'; | ||
8514 | } | ||
8515 | |||
8516 | }; | ||
8517 | |||
8518 | var momComputableOptions = { | ||
8519 | |||
8520 | // Produces format strings like "ddd M/D" -> "Fri 9/15" | ||
8521 | dayOfMonthFormat: function(momOptions, fcOptions) { | ||
8522 | var format = momOptions.longDateFormat('l'); // for the format like "M/D/YYYY" | ||
8523 | |||
8524 | // strip the year off the edge, as well as other misc non-whitespace chars | ||
8525 | format = format.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g, ''); | ||
8526 | |||
8527 | if (fcOptions.isRTL) { | ||
8528 | format += ' ddd'; // for RTL, add day-of-week to end | ||
8529 | } | ||
8530 | else { | ||
8531 | format = 'ddd ' + format; // for LTR, add day-of-week to beginning | ||
8532 | } | ||
8533 | return format; | ||
8534 | }, | ||
8535 | |||
8536 | // Produces format strings like "h:mma" -> "6:00pm" | ||
8537 | mediumTimeFormat: function(momOptions) { // can't be called `timeFormat` because collides with option | ||
8538 | return momOptions.longDateFormat('LT') | ||
8539 | .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand | ||
8540 | }, | ||
8541 | |||
8542 | // Produces format strings like "h(:mm)a" -> "6pm" / "6:30pm" | ||
8543 | smallTimeFormat: function(momOptions) { | ||
8544 | return momOptions.longDateFormat('LT') | ||
8545 | .replace(':mm', '(:mm)') | ||
8546 | .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs | ||
8547 | .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand | ||
8548 | }, | ||
8549 | |||
8550 | // Produces format strings like "h(:mm)t" -> "6p" / "6:30p" | ||
8551 | extraSmallTimeFormat: function(momOptions) { | ||
8552 | return momOptions.longDateFormat('LT') | ||
8553 | .replace(':mm', '(:mm)') | ||
8554 | .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs | ||
8555 | .replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand | ||
8556 | }, | ||
8557 | |||
8558 | // Produces format strings like "ha" / "H" -> "6pm" / "18" | ||
8559 | hourFormat: function(momOptions) { | ||
8560 | return momOptions.longDateFormat('LT') | ||
8561 | .replace(':mm', '') | ||
8562 | .replace(/(\Wmm)$/, '') // like above, but for foreign langs | ||
8563 | .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand | ||
8564 | }, | ||
8565 | |||
8566 | // Produces format strings like "h:mm" -> "6:30" (with no AM/PM) | ||
8567 | noMeridiemTimeFormat: function(momOptions) { | ||
8568 | return momOptions.longDateFormat('LT') | ||
8569 | .replace(/\s*a$/i, ''); // remove trailing AM/PM | ||
8570 | } | ||
8571 | |||
8572 | }; | ||
8573 | |||
8574 | |||
8575 | // options that should be computed off live calendar options (considers override options) | ||
8576 | var instanceComputableOptions = { // TODO: best place for this? related to lang? | ||
8577 | |||
8578 | // Produces format strings for results like "Mo 16" | ||
8579 | smallDayDateFormat: function(options) { | ||
8580 | return options.isRTL ? | ||
8581 | 'D dd' : | ||
8582 | 'dd D'; | ||
8583 | }, | ||
8584 | |||
8585 | // Produces format strings for results like "Wk 5" | ||
8586 | weekFormat: function(options) { | ||
8587 | return options.isRTL ? | ||
8588 | 'w[ ' + options.weekNumberTitle + ']' : | ||
8589 | '[' + options.weekNumberTitle + ' ]w'; | ||
8590 | }, | ||
8591 | |||
8592 | // Produces format strings for results like "Wk5" | ||
8593 | smallWeekFormat: function(options) { | ||
8594 | return options.isRTL ? | ||
8595 | 'w[' + options.weekNumberTitle + ']' : | ||
8596 | '[' + options.weekNumberTitle + ']w'; | ||
8597 | } | ||
8598 | |||
8599 | }; | ||
8600 | |||
8601 | function populateInstanceComputableOptions(options) { | ||
8602 | $.each(instanceComputableOptions, function(name, func) { | ||
8603 | if (options[name] == null) { | ||
8604 | options[name] = func(options); | ||
8605 | } | ||
8606 | }); | ||
8607 | } | ||
8608 | |||
8609 | |||
8610 | // Returns moment's internal locale data. If doesn't exist, returns English. | ||
8611 | // Works with moment-pre-2.8 | ||
8612 | function getMomentLocaleData(langCode) { | ||
8613 | var func = moment.localeData || moment.langData; | ||
8614 | return func.call(moment, langCode) || | ||
8615 | func.call(moment, 'en'); // the newer localData could return null, so fall back to en | ||
8616 | } | ||
8617 | |||
8618 | |||
8619 | // Initialize English by forcing computation of moment-derived options. | ||
8620 | // Also, sets it as the default. | ||
8621 | fc.lang('en', Calendar.englishDefaults); | ||
8622 | |||
8623 | ;; | ||
8624 | |||
8625 | /* Top toolbar area with buttons and title | ||
8626 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
8627 | // TODO: rename all header-related things to "toolbar" | ||
8628 | |||
8629 | function Header(calendar, options) { | ||
8630 | var t = this; | ||
8631 | |||
8632 | // exports | ||
8633 | t.render = render; | ||
8634 | t.destroy = destroy; | ||
8635 | t.updateTitle = updateTitle; | ||
8636 | t.activateButton = activateButton; | ||
8637 | t.deactivateButton = deactivateButton; | ||
8638 | t.disableButton = disableButton; | ||
8639 | t.enableButton = enableButton; | ||
8640 | t.getViewsWithButtons = getViewsWithButtons; | ||
8641 | |||
8642 | // locals | ||
8643 | var el = $(); | ||
8644 | var viewsWithButtons = []; | ||
8645 | var tm; | ||
8646 | |||
8647 | |||
8648 | function render() { | ||
8649 | var sections = options.header; | ||
8650 | |||
8651 | tm = options.theme ? 'ui' : 'fc'; | ||
8652 | |||
8653 | if (sections) { | ||
8654 | el = $("<div class='fc-toolbar'/>") | ||
8655 | .append(renderSection('left')) | ||
8656 | .append(renderSection('right')) | ||
8657 | .append(renderSection('center')) | ||
8658 | .append('<div class="fc-clear"/>'); | ||
8659 | |||
8660 | return el; | ||
8661 | } | ||
8662 | } | ||
8663 | |||
8664 | |||
8665 | function destroy() { | ||
8666 | el.remove(); | ||
8667 | } | ||
8668 | |||
8669 | |||
8670 | function renderSection(position) { | ||
8671 | var sectionEl = $('<div class="fc-' + position + '"/>'); | ||
8672 | var buttonStr = options.header[position]; | ||
8673 | |||
8674 | if (buttonStr) { | ||
8675 | $.each(buttonStr.split(' '), function(i) { | ||
8676 | var groupChildren = $(); | ||
8677 | var isOnlyButtons = true; | ||
8678 | var groupEl; | ||
8679 | |||
8680 | $.each(this.split(','), function(j, buttonName) { | ||
8681 | var viewSpec; | ||
8682 | var buttonClick; | ||
8683 | var overrideText; // text explicitly set by calendar's constructor options. overcomes icons | ||
8684 | var defaultText; | ||
8685 | var themeIcon; | ||
8686 | var normalIcon; | ||
8687 | var innerHtml; | ||
8688 | var classes; | ||
8689 | var button; | ||
8690 | |||
8691 | if (buttonName == 'title') { | ||
8692 | groupChildren = groupChildren.add($('<h2> </h2>')); // we always want it to take up height | ||
8693 | isOnlyButtons = false; | ||
8694 | } | ||
8695 | else { | ||
8696 | viewSpec = calendar.getViewSpec(buttonName); | ||
8697 | |||
8698 | if (viewSpec) { | ||
8699 | buttonClick = function() { | ||
8700 | calendar.changeView(buttonName); | ||
8701 | }; | ||
8702 | viewsWithButtons.push(buttonName); | ||
8703 | overrideText = viewSpec.buttonTextOverride; | ||
8704 | defaultText = viewSpec.buttonTextDefault; | ||
8705 | } | ||
8706 | else if (calendar[buttonName]) { // a calendar method | ||
8707 | buttonClick = function() { | ||
8708 | calendar[buttonName](); | ||
8709 | }; | ||
8710 | overrideText = (calendar.overrides.buttonText || {})[buttonName]; | ||
8711 | defaultText = options.buttonText[buttonName]; // everything else is considered default | ||
8712 | } | ||
8713 | |||
8714 | if (buttonClick) { | ||
8715 | |||
8716 | themeIcon = options.themeButtonIcons[buttonName]; | ||
8717 | normalIcon = options.buttonIcons[buttonName]; | ||
8718 | |||
8719 | if (overrideText) { | ||
8720 | innerHtml = htmlEscape(overrideText); | ||
8721 | } | ||
8722 | else if (themeIcon && options.theme) { | ||
8723 | innerHtml = "<span class='ui-icon ui-icon-" + themeIcon + "'></span>"; | ||
8724 | } | ||
8725 | else if (normalIcon && !options.theme) { | ||
8726 | innerHtml = "<span class='fc-icon fc-icon-" + normalIcon + "'></span>"; | ||
8727 | } | ||
8728 | else { | ||
8729 | innerHtml = htmlEscape(defaultText); | ||
8730 | } | ||
8731 | |||
8732 | classes = [ | ||
8733 | 'fc-' + buttonName + '-button', | ||
8734 | tm + '-button', | ||
8735 | tm + '-state-default' | ||
8736 | ]; | ||
8737 | |||
8738 | button = $( // type="button" so that it doesn't submit a form | ||
8739 | '<button type="button" class="' + classes.join(' ') + '">' + | ||
8740 | innerHtml + | ||
8741 | '</button>' | ||
8742 | ) | ||
8743 | .click(function() { | ||
8744 | // don't process clicks for disabled buttons | ||
8745 | if (!button.hasClass(tm + '-state-disabled')) { | ||
8746 | |||
8747 | buttonClick(); | ||
8748 | |||
8749 | // after the click action, if the button becomes the "active" tab, or disabled, | ||
8750 | // it should never have a hover class, so remove it now. | ||
8751 | if ( | ||
8752 | button.hasClass(tm + '-state-active') || | ||
8753 | button.hasClass(tm + '-state-disabled') | ||
8754 | ) { | ||
8755 | button.removeClass(tm + '-state-hover'); | ||
8756 | } | ||
8757 | } | ||
8758 | }) | ||
8759 | .mousedown(function() { | ||
8760 | // the *down* effect (mouse pressed in). | ||
8761 | // only on buttons that are not the "active" tab, or disabled | ||
8762 | button | ||
8763 | .not('.' + tm + '-state-active') | ||
8764 | .not('.' + tm + '-state-disabled') | ||
8765 | .addClass(tm + '-state-down'); | ||
8766 | }) | ||
8767 | .mouseup(function() { | ||
8768 | // undo the *down* effect | ||
8769 | button.removeClass(tm + '-state-down'); | ||
8770 | }) | ||
8771 | .hover( | ||
8772 | function() { | ||
8773 | // the *hover* effect. | ||
8774 | // only on buttons that are not the "active" tab, or disabled | ||
8775 | button | ||
8776 | .not('.' + tm + '-state-active') | ||
8777 | .not('.' + tm + '-state-disabled') | ||
8778 | .addClass(tm + '-state-hover'); | ||
8779 | }, | ||
8780 | function() { | ||
8781 | // undo the *hover* effect | ||
8782 | button | ||
8783 | .removeClass(tm + '-state-hover') | ||
8784 | .removeClass(tm + '-state-down'); // if mouseleave happens before mouseup | ||
8785 | } | ||
8786 | ); | ||
8787 | |||
8788 | groupChildren = groupChildren.add(button); | ||
8789 | } | ||
8790 | } | ||
8791 | }); | ||
8792 | |||
8793 | if (isOnlyButtons) { | ||
8794 | groupChildren | ||
8795 | .first().addClass(tm + '-corner-left').end() | ||
8796 | .last().addClass(tm + '-corner-right').end(); | ||
8797 | } | ||
8798 | |||
8799 | if (groupChildren.length > 1) { | ||
8800 | groupEl = $('<div/>'); | ||
8801 | if (isOnlyButtons) { | ||
8802 | groupEl.addClass('fc-button-group'); | ||
8803 | } | ||
8804 | groupEl.append(groupChildren); | ||
8805 | sectionEl.append(groupEl); | ||
8806 | } | ||
8807 | else { | ||
8808 | sectionEl.append(groupChildren); // 1 or 0 children | ||
8809 | } | ||
8810 | }); | ||
8811 | } | ||
8812 | |||
8813 | return sectionEl; | ||
8814 | } | ||
8815 | |||
8816 | |||
8817 | function updateTitle(text) { | ||
8818 | el.find('h2').text(text); | ||
8819 | } | ||
8820 | |||
8821 | |||
8822 | function activateButton(buttonName) { | ||
8823 | el.find('.fc-' + buttonName + '-button') | ||
8824 | .addClass(tm + '-state-active'); | ||
8825 | } | ||
8826 | |||
8827 | |||
8828 | function deactivateButton(buttonName) { | ||
8829 | el.find('.fc-' + buttonName + '-button') | ||
8830 | .removeClass(tm + '-state-active'); | ||
8831 | } | ||
8832 | |||
8833 | |||
8834 | function disableButton(buttonName) { | ||
8835 | el.find('.fc-' + buttonName + '-button') | ||
8836 | .attr('disabled', 'disabled') | ||
8837 | .addClass(tm + '-state-disabled'); | ||
8838 | } | ||
8839 | |||
8840 | |||
8841 | function enableButton(buttonName) { | ||
8842 | el.find('.fc-' + buttonName + '-button') | ||
8843 | .removeAttr('disabled') | ||
8844 | .removeClass(tm + '-state-disabled'); | ||
8845 | } | ||
8846 | |||
8847 | |||
8848 | function getViewsWithButtons() { | ||
8849 | return viewsWithButtons; | ||
8850 | } | ||
8851 | |||
8852 | } | ||
8853 | |||
8854 | ;; | ||
8855 | |||
8856 | fc.sourceNormalizers = []; | ||
8857 | fc.sourceFetchers = []; | ||
8858 | |||
8859 | var ajaxDefaults = { | ||
8860 | dataType: 'json', | ||
8861 | cache: false | ||
8862 | }; | ||
8863 | |||
8864 | var eventGUID = 1; | ||
8865 | |||
8866 | |||
8867 | function EventManager(options) { // assumed to be a calendar | ||
8868 | var t = this; | ||
8869 | |||
8870 | |||
8871 | // exports | ||
8872 | t.isFetchNeeded = isFetchNeeded; | ||
8873 | t.fetchEvents = fetchEvents; | ||
8874 | t.addEventSource = addEventSource; | ||
8875 | t.removeEventSource = removeEventSource; | ||
8876 | t.updateEvent = updateEvent; | ||
8877 | t.renderEvent = renderEvent; | ||
8878 | t.removeEvents = removeEvents; | ||
8879 | t.clientEvents = clientEvents; | ||
8880 | t.mutateEvent = mutateEvent; | ||
8881 | t.normalizeEventRange = normalizeEventRange; | ||
8882 | t.normalizeEventRangeTimes = normalizeEventRangeTimes; | ||
8883 | t.ensureVisibleEventRange = ensureVisibleEventRange; | ||
8884 | |||
8885 | |||
8886 | // imports | ||
8887 | var trigger = t.trigger; | ||
8888 | var getView = t.getView; | ||
8889 | var reportEvents = t.reportEvents; | ||
8890 | |||
8891 | |||
8892 | // locals | ||
8893 | var stickySource = { events: [] }; | ||
8894 | var sources = [ stickySource ]; | ||
8895 | var rangeStart, rangeEnd; | ||
8896 | var currentFetchID = 0; | ||
8897 | var pendingSourceCnt = 0; | ||
8898 | var loadingLevel = 0; | ||
8899 | var cache = []; // holds events that have already been expanded | ||
8900 | |||
8901 | |||
8902 | $.each( | ||
8903 | (options.events ? [ options.events ] : []).concat(options.eventSources || []), | ||
8904 | function(i, sourceInput) { | ||
8905 | var source = buildEventSource(sourceInput); | ||
8906 | if (source) { | ||
8907 | sources.push(source); | ||
8908 | } | ||
8909 | } | ||
8910 | ); | ||
8911 | |||
8912 | |||
8913 | |||
8914 | /* Fetching | ||
8915 | -----------------------------------------------------------------------------*/ | ||
8916 | |||
8917 | |||
8918 | function isFetchNeeded(start, end) { | ||
8919 | return !rangeStart || // nothing has been fetched yet? | ||
8920 | // or, a part of the new range is outside of the old range? (after normalizing) | ||
8921 | start.clone().stripZone() < rangeStart.clone().stripZone() || | ||
8922 | end.clone().stripZone() > rangeEnd.clone().stripZone(); | ||
8923 | } | ||
8924 | |||
8925 | |||
8926 | function fetchEvents(start, end) { | ||
8927 | rangeStart = start; | ||
8928 | rangeEnd = end; | ||
8929 | cache = []; | ||
8930 | var fetchID = ++currentFetchID; | ||
8931 | var len = sources.length; | ||
8932 | pendingSourceCnt = len; | ||
8933 | for (var i=0; i<len; i++) { | ||
8934 | fetchEventSource(sources[i], fetchID); | ||
8935 | } | ||
8936 | } | ||
8937 | |||
8938 | |||
8939 | function fetchEventSource(source, fetchID) { | ||
8940 | _fetchEventSource(source, function(eventInputs) { | ||
8941 | var isArraySource = $.isArray(source.events); | ||
8942 | var i, eventInput; | ||
8943 | var abstractEvent; | ||
8944 | |||
8945 | if (fetchID == currentFetchID) { | ||
8946 | |||
8947 | if (eventInputs) { | ||
8948 | for (i = 0; i < eventInputs.length; i++) { | ||
8949 | eventInput = eventInputs[i]; | ||
8950 | |||
8951 | if (isArraySource) { // array sources have already been convert to Event Objects | ||
8952 | abstractEvent = eventInput; | ||
8953 | } | ||
8954 | else { | ||
8955 | abstractEvent = buildEventFromInput(eventInput, source); | ||
8956 | } | ||
8957 | |||
8958 | if (abstractEvent) { // not false (an invalid event) | ||
8959 | cache.push.apply( | ||
8960 | cache, | ||
8961 | expandEvent(abstractEvent) // add individual expanded events to the cache | ||
8962 | ); | ||
8963 | } | ||
8964 | } | ||
8965 | } | ||
8966 | |||
8967 | pendingSourceCnt--; | ||
8968 | if (!pendingSourceCnt) { | ||
8969 | reportEvents(cache); | ||
8970 | } | ||
8971 | } | ||
8972 | }); | ||
8973 | } | ||
8974 | |||
8975 | |||
8976 | function _fetchEventSource(source, callback) { | ||
8977 | var i; | ||
8978 | var fetchers = fc.sourceFetchers; | ||
8979 | var res; | ||
8980 | |||
8981 | for (i=0; i<fetchers.length; i++) { | ||
8982 | res = fetchers[i].call( | ||
8983 | t, // this, the Calendar object | ||
8984 | source, | ||
8985 | rangeStart.clone(), | ||
8986 | rangeEnd.clone(), | ||
8987 | options.timezone, | ||
8988 | callback | ||
8989 | ); | ||
8990 | |||
8991 | if (res === true) { | ||
8992 | // the fetcher is in charge. made its own async request | ||
8993 | return; | ||
8994 | } | ||
8995 | else if (typeof res == 'object') { | ||
8996 | // the fetcher returned a new source. process it | ||
8997 | _fetchEventSource(res, callback); | ||
8998 | return; | ||
8999 | } | ||
9000 | } | ||
9001 | |||
9002 | var events = source.events; | ||
9003 | if (events) { | ||
9004 | if ($.isFunction(events)) { | ||
9005 | pushLoading(); | ||
9006 | events.call( | ||
9007 | t, // this, the Calendar object | ||
9008 | rangeStart.clone(), | ||
9009 | rangeEnd.clone(), | ||
9010 | options.timezone, | ||
9011 | function(events) { | ||
9012 | callback(events); | ||
9013 | popLoading(); | ||
9014 | } | ||
9015 | ); | ||
9016 | } | ||
9017 | else if ($.isArray(events)) { | ||
9018 | callback(events); | ||
9019 | } | ||
9020 | else { | ||
9021 | callback(); | ||
9022 | } | ||
9023 | }else{ | ||
9024 | var url = source.url; | ||
9025 | if (url) { | ||
9026 | var success = source.success; | ||
9027 | var error = source.error; | ||
9028 | var complete = source.complete; | ||
9029 | |||
9030 | // retrieve any outbound GET/POST $.ajax data from the options | ||
9031 | var customData; | ||
9032 | if ($.isFunction(source.data)) { | ||
9033 | // supplied as a function that returns a key/value object | ||
9034 | customData = source.data(); | ||
9035 | } | ||
9036 | else { | ||
9037 | // supplied as a straight key/value object | ||
9038 | customData = source.data; | ||
9039 | } | ||
9040 | |||
9041 | // use a copy of the custom data so we can modify the parameters | ||
9042 | // and not affect the passed-in object. | ||
9043 | var data = $.extend({}, customData || {}); | ||
9044 | |||
9045 | var startParam = firstDefined(source.startParam, options.startParam); | ||
9046 | var endParam = firstDefined(source.endParam, options.endParam); | ||
9047 | var timezoneParam = firstDefined(source.timezoneParam, options.timezoneParam); | ||
9048 | |||
9049 | if (startParam) { | ||
9050 | data[startParam] = rangeStart.format(); | ||
9051 | } | ||
9052 | if (endParam) { | ||
9053 | data[endParam] = rangeEnd.format(); | ||
9054 | } | ||
9055 | if (options.timezone && options.timezone != 'local') { | ||
9056 | data[timezoneParam] = options.timezone; | ||
9057 | } | ||
9058 | |||
9059 | pushLoading(); | ||
9060 | $.ajax($.extend({}, ajaxDefaults, source, { | ||
9061 | data: data, | ||
9062 | success: function(events) { | ||
9063 | events = events || []; | ||
9064 | var res = applyAll(success, this, arguments); | ||
9065 | if ($.isArray(res)) { | ||
9066 | events = res; | ||
9067 | } | ||
9068 | callback(events); | ||
9069 | }, | ||
9070 | error: function() { | ||
9071 | applyAll(error, this, arguments); | ||
9072 | callback(); | ||
9073 | }, | ||
9074 | complete: function() { | ||
9075 | applyAll(complete, this, arguments); | ||
9076 | popLoading(); | ||
9077 | } | ||
9078 | })); | ||
9079 | }else{ | ||
9080 | callback(); | ||
9081 | } | ||
9082 | } | ||
9083 | } | ||
9084 | |||
9085 | |||
9086 | |||
9087 | /* Sources | ||
9088 | -----------------------------------------------------------------------------*/ | ||
9089 | |||
9090 | |||
9091 | function addEventSource(sourceInput) { | ||
9092 | var source = buildEventSource(sourceInput); | ||
9093 | if (source) { | ||
9094 | sources.push(source); | ||
9095 | pendingSourceCnt++; | ||
9096 | fetchEventSource(source, currentFetchID); // will eventually call reportEvents | ||
9097 | } | ||
9098 | } | ||
9099 | |||
9100 | |||
9101 | function buildEventSource(sourceInput) { // will return undefined if invalid source | ||
9102 | var normalizers = fc.sourceNormalizers; | ||
9103 | var source; | ||
9104 | var i; | ||
9105 | |||
9106 | if ($.isFunction(sourceInput) || $.isArray(sourceInput)) { | ||
9107 | source = { events: sourceInput }; | ||
9108 | } | ||
9109 | else if (typeof sourceInput === 'string') { | ||
9110 | source = { url: sourceInput }; | ||
9111 | } | ||
9112 | else if (typeof sourceInput === 'object') { | ||
9113 | source = $.extend({}, sourceInput); // shallow copy | ||
9114 | } | ||
9115 | |||
9116 | if (source) { | ||
9117 | |||
9118 | // TODO: repeat code, same code for event classNames | ||
9119 | if (source.className) { | ||
9120 | if (typeof source.className === 'string') { | ||
9121 | source.className = source.className.split(/\s+/); | ||
9122 | } | ||
9123 | // otherwise, assumed to be an array | ||
9124 | } | ||
9125 | else { | ||
9126 | source.className = []; | ||
9127 | } | ||
9128 | |||
9129 | // for array sources, we convert to standard Event Objects up front | ||
9130 | if ($.isArray(source.events)) { | ||
9131 | source.origArray = source.events; // for removeEventSource | ||
9132 | source.events = $.map(source.events, function(eventInput) { | ||
9133 | return buildEventFromInput(eventInput, source); | ||
9134 | }); | ||
9135 | } | ||
9136 | |||
9137 | for (i=0; i<normalizers.length; i++) { | ||
9138 | normalizers[i].call(t, source); | ||
9139 | } | ||
9140 | |||
9141 | return source; | ||
9142 | } | ||
9143 | } | ||
9144 | |||
9145 | |||
9146 | function removeEventSource(source) { | ||
9147 | sources = $.grep(sources, function(src) { | ||
9148 | return !isSourcesEqual(src, source); | ||
9149 | }); | ||
9150 | // remove all client events from that source | ||
9151 | cache = $.grep(cache, function(e) { | ||
9152 | return !isSourcesEqual(e.source, source); | ||
9153 | }); | ||
9154 | reportEvents(cache); | ||
9155 | } | ||
9156 | |||
9157 | |||
9158 | function isSourcesEqual(source1, source2) { | ||
9159 | return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2); | ||
9160 | } | ||
9161 | |||
9162 | |||
9163 | function getSourcePrimitive(source) { | ||
9164 | return ( | ||
9165 | (typeof source === 'object') ? // a normalized event source? | ||
9166 | (source.origArray || source.googleCalendarId || source.url || source.events) : // get the primitive | ||
9167 | null | ||
9168 | ) || | ||
9169 | source; // the given argument *is* the primitive | ||
9170 | } | ||
9171 | |||
9172 | |||
9173 | |||
9174 | /* Manipulation | ||
9175 | -----------------------------------------------------------------------------*/ | ||
9176 | |||
9177 | |||
9178 | // Only ever called from the externally-facing API | ||
9179 | function updateEvent(event) { | ||
9180 | |||
9181 | // massage start/end values, even if date string values | ||
9182 | event.start = t.moment(event.start); | ||
9183 | if (event.end) { | ||
9184 | event.end = t.moment(event.end); | ||
9185 | } | ||
9186 | else { | ||
9187 | event.end = null; | ||
9188 | } | ||
9189 | |||
9190 | mutateEvent(event, getMiscEventProps(event)); // will handle start/end/allDay normalization | ||
9191 | reportEvents(cache); // reports event modifications (so we can redraw) | ||
9192 | } | ||
9193 | |||
9194 | |||
9195 | // Returns a hash of misc event properties that should be copied over to related events. | ||
9196 | function getMiscEventProps(event) { | ||
9197 | var props = {}; | ||
9198 | |||
9199 | $.each(event, function(name, val) { | ||
9200 | if (isMiscEventPropName(name)) { | ||
9201 | if (val !== undefined && isAtomic(val)) { // a defined non-object | ||
9202 | props[name] = val; | ||
9203 | } | ||
9204 | } | ||
9205 | }); | ||
9206 | |||
9207 | return props; | ||
9208 | } | ||
9209 | |||
9210 | // non-date-related, non-id-related, non-secret | ||
9211 | function isMiscEventPropName(name) { | ||
9212 | return !/^_|^(id|allDay|start|end)$/.test(name); | ||
9213 | } | ||
9214 | |||
9215 | |||
9216 | // returns the expanded events that were created | ||
9217 | function renderEvent(eventInput, stick) { | ||
9218 | var abstractEvent = buildEventFromInput(eventInput); | ||
9219 | var events; | ||
9220 | var i, event; | ||
9221 | |||
9222 | if (abstractEvent) { // not false (a valid input) | ||
9223 | events = expandEvent(abstractEvent); | ||
9224 | |||
9225 | for (i = 0; i < events.length; i++) { | ||
9226 | event = events[i]; | ||
9227 | |||
9228 | if (!event.source) { | ||
9229 | if (stick) { | ||
9230 | stickySource.events.push(event); | ||
9231 | event.source = stickySource; | ||
9232 | } | ||
9233 | cache.push(event); | ||
9234 | } | ||
9235 | } | ||
9236 | |||
9237 | reportEvents(cache); | ||
9238 | |||
9239 | return events; | ||
9240 | } | ||
9241 | |||
9242 | return []; | ||
9243 | } | ||
9244 | |||
9245 | |||
9246 | function removeEvents(filter) { | ||
9247 | var eventID; | ||
9248 | var i; | ||
9249 | |||
9250 | if (filter == null) { // null or undefined. remove all events | ||
9251 | filter = function() { return true; }; // will always match | ||
9252 | } | ||
9253 | else if (!$.isFunction(filter)) { // an event ID | ||
9254 | eventID = filter + ''; | ||
9255 | filter = function(event) { | ||
9256 | return event._id == eventID; | ||
9257 | }; | ||
9258 | } | ||
9259 | |||
9260 | // Purge event(s) from our local cache | ||
9261 | cache = $.grep(cache, filter, true); // inverse=true | ||
9262 | |||
9263 | // Remove events from array sources. | ||
9264 | // This works because they have been converted to official Event Objects up front. | ||
9265 | // (and as a result, event._id has been calculated). | ||
9266 | for (i=0; i<sources.length; i++) { | ||
9267 | if ($.isArray(sources[i].events)) { | ||
9268 | sources[i].events = $.grep(sources[i].events, filter, true); | ||
9269 | } | ||
9270 | } | ||
9271 | |||
9272 | reportEvents(cache); | ||
9273 | } | ||
9274 | |||
9275 | |||
9276 | function clientEvents(filter) { | ||
9277 | if ($.isFunction(filter)) { | ||
9278 | return $.grep(cache, filter); | ||
9279 | } | ||
9280 | else if (filter != null) { // not null, not undefined. an event ID | ||
9281 | filter += ''; | ||
9282 | return $.grep(cache, function(e) { | ||
9283 | return e._id == filter; | ||
9284 | }); | ||
9285 | } | ||
9286 | return cache; // else, return all | ||
9287 | } | ||
9288 | |||
9289 | |||
9290 | |||
9291 | /* Loading State | ||
9292 | -----------------------------------------------------------------------------*/ | ||
9293 | |||
9294 | |||
9295 | function pushLoading() { | ||
9296 | if (!(loadingLevel++)) { | ||
9297 | trigger('loading', null, true, getView()); | ||
9298 | } | ||
9299 | } | ||
9300 | |||
9301 | |||
9302 | function popLoading() { | ||
9303 | if (!(--loadingLevel)) { | ||
9304 | trigger('loading', null, false, getView()); | ||
9305 | } | ||
9306 | } | ||
9307 | |||
9308 | |||
9309 | |||
9310 | /* Event Normalization | ||
9311 | -----------------------------------------------------------------------------*/ | ||
9312 | |||
9313 | |||
9314 | // Given a raw object with key/value properties, returns an "abstract" Event object. | ||
9315 | // An "abstract" event is an event that, if recurring, will not have been expanded yet. | ||
9316 | // Will return `false` when input is invalid. | ||
9317 | // `source` is optional | ||
9318 | function buildEventFromInput(input, source) { | ||
9319 | var out = {}; | ||
9320 | var start, end; | ||
9321 | var allDay; | ||
9322 | |||
9323 | if (options.eventDataTransform) { | ||
9324 | input = options.eventDataTransform(input); | ||
9325 | } | ||
9326 | if (source && source.eventDataTransform) { | ||
9327 | input = source.eventDataTransform(input); | ||
9328 | } | ||
9329 | |||
9330 | // Copy all properties over to the resulting object. | ||
9331 | // The special-case properties will be copied over afterwards. | ||
9332 | $.extend(out, input); | ||
9333 | |||
9334 | if (source) { | ||
9335 | out.source = source; | ||
9336 | } | ||
9337 | |||
9338 | out._id = input._id || (input.id === undefined ? '_fc' + eventGUID++ : input.id + ''); | ||
9339 | |||
9340 | if (input.className) { | ||
9341 | if (typeof input.className == 'string') { | ||
9342 | out.className = input.className.split(/\s+/); | ||
9343 | } | ||
9344 | else { // assumed to be an array | ||
9345 | out.className = input.className; | ||
9346 | } | ||
9347 | } | ||
9348 | else { | ||
9349 | out.className = []; | ||
9350 | } | ||
9351 | |||
9352 | start = input.start || input.date; // "date" is an alias for "start" | ||
9353 | end = input.end; | ||
9354 | |||
9355 | // parse as a time (Duration) if applicable | ||
9356 | if (isTimeString(start)) { | ||
9357 | start = moment.duration(start); | ||
9358 | } | ||
9359 | if (isTimeString(end)) { | ||
9360 | end = moment.duration(end); | ||
9361 | } | ||
9362 | |||
9363 | if (input.dow || moment.isDuration(start) || moment.isDuration(end)) { | ||
9364 | |||
9365 | // the event is "abstract" (recurring) so don't calculate exact start/end dates just yet | ||
9366 | out.start = start ? moment.duration(start) : null; // will be a Duration or null | ||
9367 | out.end = end ? moment.duration(end) : null; // will be a Duration or null | ||
9368 | out._recurring = true; // our internal marker | ||
9369 | } | ||
9370 | else { | ||
9371 | |||
9372 | if (start) { | ||
9373 | start = t.moment(start); | ||
9374 | if (!start.isValid()) { | ||
9375 | return false; | ||
9376 | } | ||
9377 | } | ||
9378 | |||
9379 | if (end) { | ||
9380 | end = t.moment(end); | ||
9381 | if (!end.isValid()) { | ||
9382 | end = null; // let defaults take over | ||
9383 | } | ||
9384 | } | ||
9385 | |||
9386 | allDay = input.allDay; | ||
9387 | if (allDay === undefined) { // still undefined? fallback to default | ||
9388 | allDay = firstDefined( | ||
9389 | source ? source.allDayDefault : undefined, | ||
9390 | options.allDayDefault | ||
9391 | ); | ||
9392 | // still undefined? normalizeEventRange will calculate it | ||
9393 | } | ||
9394 | |||
9395 | assignDatesToEvent(start, end, allDay, out); | ||
9396 | } | ||
9397 | |||
9398 | return out; | ||
9399 | } | ||
9400 | |||
9401 | |||
9402 | // Normalizes and assigns the given dates to the given partially-formed event object. | ||
9403 | // NOTE: mutates the given start/end moments. does not make a copy. | ||
9404 | function assignDatesToEvent(start, end, allDay, event) { | ||
9405 | event.start = start; | ||
9406 | event.end = end; | ||
9407 | event.allDay = allDay; | ||
9408 | normalizeEventRange(event); | ||
9409 | backupEventDates(event); | ||
9410 | } | ||
9411 | |||
9412 | |||
9413 | // Ensures proper values for allDay/start/end. Accepts an Event object, or a plain object with event-ish properties. | ||
9414 | // NOTE: Will modify the given object. | ||
9415 | function normalizeEventRange(props) { | ||
9416 | |||
9417 | normalizeEventRangeTimes(props); | ||
9418 | |||
9419 | if (props.end && !props.end.isAfter(props.start)) { | ||
9420 | props.end = null; | ||
9421 | } | ||
9422 | |||
9423 | if (!props.end) { | ||
9424 | if (options.forceEventDuration) { | ||
9425 | props.end = t.getDefaultEventEnd(props.allDay, props.start); | ||
9426 | } | ||
9427 | else { | ||
9428 | props.end = null; | ||
9429 | } | ||
9430 | } | ||
9431 | } | ||
9432 | |||
9433 | |||
9434 | // Ensures the allDay property exists and the timeliness of the start/end dates are consistent | ||
9435 | function normalizeEventRangeTimes(range) { | ||
9436 | if (range.allDay == null) { | ||
9437 | range.allDay = !(range.start.hasTime() || (range.end && range.end.hasTime())); | ||
9438 | } | ||
9439 | |||
9440 | if (range.allDay) { | ||
9441 | range.start.stripTime(); | ||
9442 | if (range.end) { | ||
9443 | // TODO: consider nextDayThreshold here? If so, will require a lot of testing and adjustment | ||
9444 | range.end.stripTime(); | ||
9445 | } | ||
9446 | } | ||
9447 | else { | ||
9448 | if (!range.start.hasTime()) { | ||
9449 | range.start = t.rezoneDate(range.start); // will assign a 00:00 time | ||
9450 | } | ||
9451 | if (range.end && !range.end.hasTime()) { | ||
9452 | range.end = t.rezoneDate(range.end); // will assign a 00:00 time | ||
9453 | } | ||
9454 | } | ||
9455 | } | ||
9456 | |||
9457 | |||
9458 | // If `range` is a proper range with a start and end, returns the original object. | ||
9459 | // If missing an end, computes a new range with an end, computing it as if it were an event. | ||
9460 | // TODO: make this a part of the event -> eventRange system | ||
9461 | function ensureVisibleEventRange(range) { | ||
9462 | var allDay; | ||
9463 | |||
9464 | if (!range.end) { | ||
9465 | |||
9466 | allDay = range.allDay; // range might be more event-ish than we think | ||
9467 | if (allDay == null) { | ||
9468 | allDay = !range.start.hasTime(); | ||
9469 | } | ||
9470 | |||
9471 | range = $.extend({}, range); // make a copy, copying over other misc properties | ||
9472 | range.end = t.getDefaultEventEnd(allDay, range.start); | ||
9473 | } | ||
9474 | return range; | ||
9475 | } | ||
9476 | |||
9477 | |||
9478 | // If the given event is a recurring event, break it down into an array of individual instances. | ||
9479 | // If not a recurring event, return an array with the single original event. | ||
9480 | // If given a falsy input (probably because of a failed buildEventFromInput call), returns an empty array. | ||
9481 | // HACK: can override the recurring window by providing custom rangeStart/rangeEnd (for businessHours). | ||
9482 | function expandEvent(abstractEvent, _rangeStart, _rangeEnd) { | ||
9483 | var events = []; | ||
9484 | var dowHash; | ||
9485 | var dow; | ||
9486 | var i; | ||
9487 | var date; | ||
9488 | var startTime, endTime; | ||
9489 | var start, end; | ||
9490 | var event; | ||
9491 | |||
9492 | _rangeStart = _rangeStart || rangeStart; | ||
9493 | _rangeEnd = _rangeEnd || rangeEnd; | ||
9494 | |||
9495 | if (abstractEvent) { | ||
9496 | if (abstractEvent._recurring) { | ||
9497 | |||
9498 | // make a boolean hash as to whether the event occurs on each day-of-week | ||
9499 | if ((dow = abstractEvent.dow)) { | ||
9500 | dowHash = {}; | ||
9501 | for (i = 0; i < dow.length; i++) { | ||
9502 | dowHash[dow[i]] = true; | ||
9503 | } | ||
9504 | } | ||
9505 | |||
9506 | // iterate through every day in the current range | ||
9507 | date = _rangeStart.clone().stripTime(); // holds the date of the current day | ||
9508 | while (date.isBefore(_rangeEnd)) { | ||
9509 | |||
9510 | if (!dowHash || dowHash[date.day()]) { // if everyday, or this particular day-of-week | ||
9511 | |||
9512 | startTime = abstractEvent.start; // the stored start and end properties are times (Durations) | ||
9513 | endTime = abstractEvent.end; // " | ||
9514 | start = date.clone(); | ||
9515 | end = null; | ||
9516 | |||
9517 | if (startTime) { | ||
9518 | start = start.time(startTime); | ||
9519 | } | ||
9520 | if (endTime) { | ||
9521 | end = date.clone().time(endTime); | ||
9522 | } | ||
9523 | |||
9524 | event = $.extend({}, abstractEvent); // make a copy of the original | ||
9525 | assignDatesToEvent( | ||
9526 | start, end, | ||
9527 | !startTime && !endTime, // allDay? | ||
9528 | event | ||
9529 | ); | ||
9530 | events.push(event); | ||
9531 | } | ||
9532 | |||
9533 | date.add(1, 'days'); | ||
9534 | } | ||
9535 | } | ||
9536 | else { | ||
9537 | events.push(abstractEvent); // return the original event. will be a one-item array | ||
9538 | } | ||
9539 | } | ||
9540 | |||
9541 | return events; | ||
9542 | } | ||
9543 | |||
9544 | |||
9545 | |||
9546 | /* Event Modification Math | ||
9547 | -----------------------------------------------------------------------------------------*/ | ||
9548 | |||
9549 | |||
9550 | // Modifies an event and all related events by applying the given properties. | ||
9551 | // Special date-diffing logic is used for manipulation of dates. | ||
9552 | // If `props` does not contain start/end dates, the updated values are assumed to be the event's current start/end. | ||
9553 | // All date comparisons are done against the event's pristine _start and _end dates. | ||
9554 | // Returns an object with delta information and a function to undo all operations. | ||
9555 | // For making computations in a granularity greater than day/time, specify largeUnit. | ||
9556 | // NOTE: The given `newProps` might be mutated for normalization purposes. | ||
9557 | function mutateEvent(event, newProps, largeUnit) { | ||
9558 | var miscProps = {}; | ||
9559 | var oldProps; | ||
9560 | var clearEnd; | ||
9561 | var startDelta; | ||
9562 | var endDelta; | ||
9563 | var durationDelta; | ||
9564 | var undoFunc; | ||
9565 | |||
9566 | // diffs the dates in the appropriate way, returning a duration | ||
9567 | function diffDates(date1, date0) { // date1 - date0 | ||
9568 | if (largeUnit) { | ||
9569 | return diffByUnit(date1, date0, largeUnit); | ||
9570 | } | ||
9571 | else if (newProps.allDay) { | ||
9572 | return diffDay(date1, date0); | ||
9573 | } | ||
9574 | else { | ||
9575 | return diffDayTime(date1, date0); | ||
9576 | } | ||
9577 | } | ||
9578 | |||
9579 | newProps = newProps || {}; | ||
9580 | |||
9581 | // normalize new date-related properties | ||
9582 | if (!newProps.start) { | ||
9583 | newProps.start = event.start.clone(); | ||
9584 | } | ||
9585 | if (newProps.end === undefined) { | ||
9586 | newProps.end = event.end ? event.end.clone() : null; | ||
9587 | } | ||
9588 | if (newProps.allDay == null) { // is null or undefined? | ||
9589 | newProps.allDay = event.allDay; | ||
9590 | } | ||
9591 | normalizeEventRange(newProps); | ||
9592 | |||
9593 | // create normalized versions of the original props to compare against | ||
9594 | // need a real end value, for diffing | ||
9595 | oldProps = { | ||
9596 | start: event._start.clone(), | ||
9597 | end: event._end ? event._end.clone() : t.getDefaultEventEnd(event._allDay, event._start), | ||
9598 | allDay: newProps.allDay // normalize the dates in the same regard as the new properties | ||
9599 | }; | ||
9600 | normalizeEventRange(oldProps); | ||
9601 | |||
9602 | // need to clear the end date if explicitly changed to null | ||
9603 | clearEnd = event._end !== null && newProps.end === null; | ||
9604 | |||
9605 | // compute the delta for moving the start date | ||
9606 | startDelta = diffDates(newProps.start, oldProps.start); | ||
9607 | |||
9608 | // compute the delta for moving the end date | ||
9609 | if (newProps.end) { | ||
9610 | endDelta = diffDates(newProps.end, oldProps.end); | ||
9611 | durationDelta = endDelta.subtract(startDelta); | ||
9612 | } | ||
9613 | else { | ||
9614 | durationDelta = null; | ||
9615 | } | ||
9616 | |||
9617 | // gather all non-date-related properties | ||
9618 | $.each(newProps, function(name, val) { | ||
9619 | if (isMiscEventPropName(name)) { | ||
9620 | if (val !== undefined) { | ||
9621 | miscProps[name] = val; | ||
9622 | } | ||
9623 | } | ||
9624 | }); | ||
9625 | |||
9626 | // apply the operations to the event and all related events | ||
9627 | undoFunc = mutateEvents( | ||
9628 | clientEvents(event._id), // get events with this ID | ||
9629 | clearEnd, | ||
9630 | newProps.allDay, | ||
9631 | startDelta, | ||
9632 | durationDelta, | ||
9633 | miscProps | ||
9634 | ); | ||
9635 | |||
9636 | return { | ||
9637 | dateDelta: startDelta, | ||
9638 | durationDelta: durationDelta, | ||
9639 | undo: undoFunc | ||
9640 | }; | ||
9641 | } | ||
9642 | |||
9643 | |||
9644 | // Modifies an array of events in the following ways (operations are in order): | ||
9645 | // - clear the event's `end` | ||
9646 | // - convert the event to allDay | ||
9647 | // - add `dateDelta` to the start and end | ||
9648 | // - add `durationDelta` to the event's duration | ||
9649 | // - assign `miscProps` to the event | ||
9650 | // | ||
9651 | // Returns a function that can be called to undo all the operations. | ||
9652 | // | ||
9653 | // TODO: don't use so many closures. possible memory issues when lots of events with same ID. | ||
9654 | // | ||
9655 | function mutateEvents(events, clearEnd, allDay, dateDelta, durationDelta, miscProps) { | ||
9656 | var isAmbigTimezone = t.getIsAmbigTimezone(); | ||
9657 | var undoFunctions = []; | ||
9658 | |||
9659 | // normalize zero-length deltas to be null | ||
9660 | if (dateDelta && !dateDelta.valueOf()) { dateDelta = null; } | ||
9661 | if (durationDelta && !durationDelta.valueOf()) { durationDelta = null; } | ||
9662 | |||
9663 | $.each(events, function(i, event) { | ||
9664 | var oldProps; | ||
9665 | var newProps; | ||
9666 | |||
9667 | // build an object holding all the old values, both date-related and misc. | ||
9668 | // for the undo function. | ||
9669 | oldProps = { | ||
9670 | start: event.start.clone(), | ||
9671 | end: event.end ? event.end.clone() : null, | ||
9672 | allDay: event.allDay | ||
9673 | }; | ||
9674 | $.each(miscProps, function(name) { | ||
9675 | oldProps[name] = event[name]; | ||
9676 | }); | ||
9677 | |||
9678 | // new date-related properties. work off the original date snapshot. | ||
9679 | // ok to use references because they will be thrown away when backupEventDates is called. | ||
9680 | newProps = { | ||
9681 | start: event._start, | ||
9682 | end: event._end, | ||
9683 | allDay: allDay // normalize the dates in the same regard as the new properties | ||
9684 | }; | ||
9685 | normalizeEventRange(newProps); // massages start/end/allDay | ||
9686 | |||
9687 | // strip or ensure the end date | ||
9688 | if (clearEnd) { | ||
9689 | newProps.end = null; | ||
9690 | } | ||
9691 | else if (durationDelta && !newProps.end) { // the duration translation requires an end date | ||
9692 | newProps.end = t.getDefaultEventEnd(newProps.allDay, newProps.start); | ||
9693 | } | ||
9694 | |||
9695 | if (dateDelta) { | ||
9696 | newProps.start.add(dateDelta); | ||
9697 | if (newProps.end) { | ||
9698 | newProps.end.add(dateDelta); | ||
9699 | } | ||
9700 | } | ||
9701 | |||
9702 | if (durationDelta) { | ||
9703 | newProps.end.add(durationDelta); // end already ensured above | ||
9704 | } | ||
9705 | |||
9706 | // if the dates have changed, and we know it is impossible to recompute the | ||
9707 | // timezone offsets, strip the zone. | ||
9708 | if ( | ||
9709 | isAmbigTimezone && | ||
9710 | !newProps.allDay && | ||
9711 | (dateDelta || durationDelta) | ||
9712 | ) { | ||
9713 | newProps.start.stripZone(); | ||
9714 | if (newProps.end) { | ||
9715 | newProps.end.stripZone(); | ||
9716 | } | ||
9717 | } | ||
9718 | |||
9719 | $.extend(event, miscProps, newProps); // copy over misc props, then date-related props | ||
9720 | backupEventDates(event); // regenerate internal _start/_end/_allDay | ||
9721 | |||
9722 | undoFunctions.push(function() { | ||
9723 | $.extend(event, oldProps); | ||
9724 | backupEventDates(event); // regenerate internal _start/_end/_allDay | ||
9725 | }); | ||
9726 | }); | ||
9727 | |||
9728 | return function() { | ||
9729 | for (var i = 0; i < undoFunctions.length; i++) { | ||
9730 | undoFunctions[i](); | ||
9731 | } | ||
9732 | }; | ||
9733 | } | ||
9734 | |||
9735 | |||
9736 | /* Business Hours | ||
9737 | -----------------------------------------------------------------------------------------*/ | ||
9738 | |||
9739 | t.getBusinessHoursEvents = getBusinessHoursEvents; | ||
9740 | |||
9741 | |||
9742 | // Returns an array of events as to when the business hours occur in the given view. | ||
9743 | // Abuse of our event system :( | ||
9744 | function getBusinessHoursEvents(wholeDay) { | ||
9745 | var optionVal = options.businessHours; | ||
9746 | var defaultVal = { | ||
9747 | className: 'fc-nonbusiness', | ||
9748 | start: '09:00', | ||
9749 | end: '17:00', | ||
9750 | dow: [ 1, 2, 3, 4, 5 ], // monday - friday | ||
9751 | rendering: 'inverse-background' | ||
9752 | }; | ||
9753 | var view = t.getView(); | ||
9754 | var eventInput; | ||
9755 | |||
9756 | if (optionVal) { // `true` (which means "use the defaults") or an override object | ||
9757 | eventInput = $.extend( | ||
9758 | {}, // copy to a new object in either case | ||
9759 | defaultVal, | ||
9760 | typeof optionVal === 'object' ? optionVal : {} // override the defaults | ||
9761 | ); | ||
9762 | } | ||
9763 | |||
9764 | if (eventInput) { | ||
9765 | |||
9766 | // if a whole-day series is requested, clear the start/end times | ||
9767 | if (wholeDay) { | ||
9768 | eventInput.start = null; | ||
9769 | eventInput.end = null; | ||
9770 | } | ||
9771 | |||
9772 | return expandEvent( | ||
9773 | buildEventFromInput(eventInput), | ||
9774 | view.start, | ||
9775 | view.end | ||
9776 | ); | ||
9777 | } | ||
9778 | |||
9779 | return []; | ||
9780 | } | ||
9781 | |||
9782 | |||
9783 | /* Overlapping / Constraining | ||
9784 | -----------------------------------------------------------------------------------------*/ | ||
9785 | |||
9786 | t.isEventRangeAllowed = isEventRangeAllowed; | ||
9787 | t.isSelectionRangeAllowed = isSelectionRangeAllowed; | ||
9788 | t.isExternalDropRangeAllowed = isExternalDropRangeAllowed; | ||
9789 | |||
9790 | |||
9791 | function isEventRangeAllowed(range, event) { | ||
9792 | var source = event.source || {}; | ||
9793 | var constraint = firstDefined( | ||
9794 | event.constraint, | ||
9795 | source.constraint, | ||
9796 | options.eventConstraint | ||
9797 | ); | ||
9798 | var overlap = firstDefined( | ||
9799 | event.overlap, | ||
9800 | source.overlap, | ||
9801 | options.eventOverlap | ||
9802 | ); | ||
9803 | |||
9804 | range = ensureVisibleEventRange(range); // ensure a proper range with an end for isRangeAllowed | ||
9805 | |||
9806 | return isRangeAllowed(range, constraint, overlap, event); | ||
9807 | } | ||
9808 | |||
9809 | |||
9810 | function isSelectionRangeAllowed(range) { | ||
9811 | return isRangeAllowed(range, options.selectConstraint, options.selectOverlap); | ||
9812 | } | ||
9813 | |||
9814 | |||
9815 | // when `eventProps` is defined, consider this an event. | ||
9816 | // `eventProps` can contain misc non-date-related info about the event. | ||
9817 | function isExternalDropRangeAllowed(range, eventProps) { | ||
9818 | var eventInput; | ||
9819 | var event; | ||
9820 | |||
9821 | // note: very similar logic is in View's reportExternalDrop | ||
9822 | if (eventProps) { | ||
9823 | eventInput = $.extend({}, eventProps, range); | ||
9824 | event = expandEvent(buildEventFromInput(eventInput))[0]; | ||
9825 | } | ||
9826 | |||
9827 | if (event) { | ||
9828 | return isEventRangeAllowed(range, event); | ||
9829 | } | ||
9830 | else { // treat it as a selection | ||
9831 | |||
9832 | range = ensureVisibleEventRange(range); // ensure a proper range with an end for isSelectionRangeAllowed | ||
9833 | |||
9834 | return isSelectionRangeAllowed(range); | ||
9835 | } | ||
9836 | } | ||
9837 | |||
9838 | |||
9839 | // Returns true if the given range (caused by an event drop/resize or a selection) is allowed to exist | ||
9840 | // according to the constraint/overlap settings. | ||
9841 | // `event` is not required if checking a selection. | ||
9842 | function isRangeAllowed(range, constraint, overlap, event) { | ||
9843 | var constraintEvents; | ||
9844 | var anyContainment; | ||
9845 | var peerEvents; | ||
9846 | var i, peerEvent; | ||
9847 | var peerOverlap; | ||
9848 | |||
9849 | // normalize. fyi, we're normalizing in too many places :( | ||
9850 | range = $.extend({}, range); // copy all properties in case there are misc non-date properties | ||
9851 | range.start = range.start.clone().stripZone(); | ||
9852 | range.end = range.end.clone().stripZone(); | ||
9853 | |||
9854 | // the range must be fully contained by at least one of produced constraint events | ||
9855 | if (constraint != null) { | ||
9856 | |||
9857 | // not treated as an event! intermediate data structure | ||
9858 | // TODO: use ranges in the future | ||
9859 | constraintEvents = constraintToEvents(constraint); | ||
9860 | |||
9861 | anyContainment = false; | ||
9862 | for (i = 0; i < constraintEvents.length; i++) { | ||
9863 | if (eventContainsRange(constraintEvents[i], range)) { | ||
9864 | anyContainment = true; | ||
9865 | break; | ||
9866 | } | ||
9867 | } | ||
9868 | |||
9869 | if (!anyContainment) { | ||
9870 | return false; | ||
9871 | } | ||
9872 | } | ||
9873 | |||
9874 | peerEvents = t.getPeerEvents(event, range); | ||
9875 | |||
9876 | for (i = 0; i < peerEvents.length; i++) { | ||
9877 | peerEvent = peerEvents[i]; | ||
9878 | |||
9879 | // there needs to be an actual intersection before disallowing anything | ||
9880 | if (eventIntersectsRange(peerEvent, range)) { | ||
9881 | |||
9882 | // evaluate overlap for the given range and short-circuit if necessary | ||
9883 | if (overlap === false) { | ||
9884 | return false; | ||
9885 | } | ||
9886 | // if the event's overlap is a test function, pass the peer event in question as the first param | ||
9887 | else if (typeof overlap === 'function' && !overlap(peerEvent, event)) { | ||
9888 | return false; | ||
9889 | } | ||
9890 | |||
9891 | // if we are computing if the given range is allowable for an event, consider the other event's | ||
9892 | // EventObject-specific or Source-specific `overlap` property | ||
9893 | if (event) { | ||
9894 | peerOverlap = firstDefined( | ||
9895 | peerEvent.overlap, | ||
9896 | (peerEvent.source || {}).overlap | ||
9897 | // we already considered the global `eventOverlap` | ||
9898 | ); | ||
9899 | if (peerOverlap === false) { | ||
9900 | return false; | ||
9901 | } | ||
9902 | // if the peer event's overlap is a test function, pass the subject event as the first param | ||
9903 | if (typeof peerOverlap === 'function' && !peerOverlap(event, peerEvent)) { | ||
9904 | return false; | ||
9905 | } | ||
9906 | } | ||
9907 | } | ||
9908 | } | ||
9909 | |||
9910 | return true; | ||
9911 | } | ||
9912 | |||
9913 | |||
9914 | // Given an event input from the API, produces an array of event objects. Possible event inputs: | ||
9915 | // 'businessHours' | ||
9916 | // An event ID (number or string) | ||
9917 | // An object with specific start/end dates or a recurring event (like what businessHours accepts) | ||
9918 | function constraintToEvents(constraintInput) { | ||
9919 | |||
9920 | if (constraintInput === 'businessHours') { | ||
9921 | return getBusinessHoursEvents(); | ||
9922 | } | ||
9923 | |||
9924 | if (typeof constraintInput === 'object') { | ||
9925 | return expandEvent(buildEventFromInput(constraintInput)); | ||
9926 | } | ||
9927 | |||
9928 | return clientEvents(constraintInput); // probably an ID | ||
9929 | } | ||
9930 | |||
9931 | |||
9932 | // Does the event's date range fully contain the given range? | ||
9933 | // start/end already assumed to have stripped zones :( | ||
9934 | function eventContainsRange(event, range) { | ||
9935 | var eventStart = event.start.clone().stripZone(); | ||
9936 | var eventEnd = t.getEventEnd(event).stripZone(); | ||
9937 | |||
9938 | return range.start >= eventStart && range.end <= eventEnd; | ||
9939 | } | ||
9940 | |||
9941 | |||
9942 | // Does the event's date range intersect with the given range? | ||
9943 | // start/end already assumed to have stripped zones :( | ||
9944 | function eventIntersectsRange(event, range) { | ||
9945 | var eventStart = event.start.clone().stripZone(); | ||
9946 | var eventEnd = t.getEventEnd(event).stripZone(); | ||
9947 | |||
9948 | return range.start < eventEnd && range.end > eventStart; | ||
9949 | } | ||
9950 | |||
9951 | |||
9952 | t.getEventCache = function() { | ||
9953 | return cache; | ||
9954 | }; | ||
9955 | |||
9956 | } | ||
9957 | |||
9958 | |||
9959 | // Returns a list of events that the given event should be compared against when being considered for a move to | ||
9960 | // the specified range. Attached to the Calendar's prototype because EventManager is a mixin for a Calendar. | ||
9961 | Calendar.prototype.getPeerEvents = function(event, range) { | ||
9962 | var cache = this.getEventCache(); | ||
9963 | var peerEvents = []; | ||
9964 | var i, otherEvent; | ||
9965 | |||
9966 | for (i = 0; i < cache.length; i++) { | ||
9967 | otherEvent = cache[i]; | ||
9968 | if ( | ||
9969 | !event || | ||
9970 | event._id !== otherEvent._id // don't compare the event to itself or other related [repeating] events | ||
9971 | ) { | ||
9972 | peerEvents.push(otherEvent); | ||
9973 | } | ||
9974 | } | ||
9975 | |||
9976 | return peerEvents; | ||
9977 | }; | ||
9978 | |||
9979 | |||
9980 | // updates the "backup" properties, which are preserved in order to compute diffs later on. | ||
9981 | function backupEventDates(event) { | ||
9982 | event._allDay = event.allDay; | ||
9983 | event._start = event.start.clone(); | ||
9984 | event._end = event.end ? event.end.clone() : null; | ||
9985 | } | ||
9986 | |||
9987 | ;; | ||
9988 | |||
9989 | /* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells. | ||
9990 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
9991 | // It is a manager for a DayGrid subcomponent, which does most of the heavy lifting. | ||
9992 | // It is responsible for managing width/height. | ||
9993 | |||
9994 | var BasicView = fcViews.basic = View.extend({ | ||
9995 | |||
9996 | dayGrid: null, // the main subcomponent that does most of the heavy lifting | ||
9997 | |||
9998 | dayNumbersVisible: false, // display day numbers on each day cell? | ||
9999 | weekNumbersVisible: false, // display week numbers along the side? | ||
10000 | |||
10001 | weekNumberWidth: null, // width of all the week-number cells running down the side | ||
10002 | |||
10003 | headRowEl: null, // the fake row element of the day-of-week header | ||
10004 | |||
10005 | |||
10006 | initialize: function() { | ||
10007 | this.dayGrid = new DayGrid(this); | ||
10008 | this.coordMap = this.dayGrid.coordMap; // the view's date-to-cell mapping is identical to the subcomponent's | ||
10009 | }, | ||
10010 | |||
10011 | |||
10012 | // Sets the display range and computes all necessary dates | ||
10013 | setRange: function(range) { | ||
10014 | View.prototype.setRange.call(this, range); // call the super-method | ||
10015 | |||
10016 | this.dayGrid.breakOnWeeks = /year|month|week/.test(this.intervalUnit); // do before setRange | ||
10017 | this.dayGrid.setRange(range); | ||
10018 | }, | ||
10019 | |||
10020 | |||
10021 | // Compute the value to feed into setRange. Overrides superclass. | ||
10022 | computeRange: function(date) { | ||
10023 | var range = View.prototype.computeRange.call(this, date); // get value from the super-method | ||
10024 | |||
10025 | // year and month views should be aligned with weeks. this is already done for week | ||
10026 | if (/year|month/.test(range.intervalUnit)) { | ||
10027 | range.start.startOf('week'); | ||
10028 | range.start = this.skipHiddenDays(range.start); | ||
10029 | |||
10030 | // make end-of-week if not already | ||
10031 | if (range.end.weekday()) { | ||
10032 | range.end.add(1, 'week').startOf('week'); | ||
10033 | range.end = this.skipHiddenDays(range.end, -1, true); // exclusively move backwards | ||
10034 | } | ||
10035 | } | ||
10036 | |||
10037 | return range; | ||
10038 | }, | ||
10039 | |||
10040 | |||
10041 | // Renders the view into `this.el`, which should already be assigned | ||
10042 | render: function() { | ||
10043 | |||
10044 | this.dayNumbersVisible = this.dayGrid.rowCnt > 1; // TODO: make grid responsible | ||
10045 | this.weekNumbersVisible = this.opt('weekNumbers'); | ||
10046 | this.dayGrid.numbersVisible = this.dayNumbersVisible || this.weekNumbersVisible; | ||
10047 | |||
10048 | this.el.addClass('fc-basic-view').html(this.renderHtml()); | ||
10049 | |||
10050 | this.headRowEl = this.el.find('thead .fc-row'); | ||
10051 | |||
10052 | this.scrollerEl = this.el.find('.fc-day-grid-container'); | ||
10053 | this.dayGrid.coordMap.containerEl = this.scrollerEl; // constrain clicks/etc to the dimensions of the scroller | ||
10054 | |||
10055 | this.dayGrid.setElement(this.el.find('.fc-day-grid')); | ||
10056 | this.dayGrid.renderDates(this.hasRigidRows()); | ||
10057 | }, | ||
10058 | |||
10059 | |||
10060 | // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering, | ||
10061 | // always completely kill the dayGrid's rendering. | ||
10062 | destroy: function() { | ||
10063 | this.dayGrid.destroyDates(); | ||
10064 | this.dayGrid.removeElement(); | ||
10065 | }, | ||
10066 | |||
10067 | |||
10068 | renderBusinessHours: function() { | ||
10069 | this.dayGrid.renderBusinessHours(); | ||
10070 | }, | ||
10071 | |||
10072 | |||
10073 | // Builds the HTML skeleton for the view. | ||
10074 | // The day-grid component will render inside of a container defined by this HTML. | ||
10075 | renderHtml: function() { | ||
10076 | return '' + | ||
10077 | '<table>' + | ||
10078 | '<thead class="fc-head">' + | ||
10079 | '<tr>' + | ||
10080 | '<td class="' + this.widgetHeaderClass + '">' + | ||
10081 | this.dayGrid.headHtml() + // render the day-of-week headers | ||
10082 | '</td>' + | ||
10083 | '</tr>' + | ||
10084 | '</thead>' + | ||
10085 | '<tbody class="fc-body">' + | ||
10086 | '<tr>' + | ||
10087 | '<td class="' + this.widgetContentClass + '">' + | ||
10088 | '<div class="fc-day-grid-container">' + | ||
10089 | '<div class="fc-day-grid"/>' + | ||
10090 | '</div>' + | ||
10091 | '</td>' + | ||
10092 | '</tr>' + | ||
10093 | '</tbody>' + | ||
10094 | '</table>'; | ||
10095 | }, | ||
10096 | |||
10097 | |||
10098 | // Generates the HTML that will go before the day-of week header cells. | ||
10099 | // Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL. | ||
10100 | headIntroHtml: function() { | ||
10101 | if (this.weekNumbersVisible) { | ||
10102 | return '' + | ||
10103 | '<th class="fc-week-number ' + this.widgetHeaderClass + '" ' + this.weekNumberStyleAttr() + '>' + | ||
10104 | '<span>' + // needed for matchCellWidths | ||
10105 | htmlEscape(this.opt('weekNumberTitle')) + | ||
10106 | '</span>' + | ||
10107 | '</th>'; | ||
10108 | } | ||
10109 | }, | ||
10110 | |||
10111 | |||
10112 | // Generates the HTML that will go before content-skeleton cells that display the day/week numbers. | ||
10113 | // Queried by the DayGrid subcomponent. Ordering depends on isRTL. | ||
10114 | numberIntroHtml: function(row) { | ||
10115 | if (this.weekNumbersVisible) { | ||
10116 | return '' + | ||
10117 | '<td class="fc-week-number" ' + this.weekNumberStyleAttr() + '>' + | ||
10118 | '<span>' + // needed for matchCellWidths | ||
10119 | this.dayGrid.getCell(row, 0).start.format('w') + | ||
10120 | '</span>' + | ||
10121 | '</td>'; | ||
10122 | } | ||
10123 | }, | ||
10124 | |||
10125 | |||
10126 | // Generates the HTML that goes before the day bg cells for each day-row. | ||
10127 | // Queried by the DayGrid subcomponent. Ordering depends on isRTL. | ||
10128 | dayIntroHtml: function() { | ||
10129 | if (this.weekNumbersVisible) { | ||
10130 | return '<td class="fc-week-number ' + this.widgetContentClass + '" ' + | ||
10131 | this.weekNumberStyleAttr() + '></td>'; | ||
10132 | } | ||
10133 | }, | ||
10134 | |||
10135 | |||
10136 | // Generates the HTML that goes before every other type of row generated by DayGrid. Ordering depends on isRTL. | ||
10137 | // Affects helper-skeleton and highlight-skeleton rows. | ||
10138 | introHtml: function() { | ||
10139 | if (this.weekNumbersVisible) { | ||
10140 | return '<td class="fc-week-number" ' + this.weekNumberStyleAttr() + '></td>'; | ||
10141 | } | ||
10142 | }, | ||
10143 | |||
10144 | |||
10145 | // Generates the HTML for the <td>s of the "number" row in the DayGrid's content skeleton. | ||
10146 | // The number row will only exist if either day numbers or week numbers are turned on. | ||
10147 | numberCellHtml: function(cell) { | ||
10148 | var date = cell.start; | ||
10149 | var classes; | ||
10150 | |||
10151 | if (!this.dayNumbersVisible) { // if there are week numbers but not day numbers | ||
10152 | return '<td/>'; // will create an empty space above events :( | ||
10153 | } | ||
10154 | |||
10155 | classes = this.dayGrid.getDayClasses(date); | ||
10156 | classes.unshift('fc-day-number'); | ||
10157 | |||
10158 | return '' + | ||
10159 | '<td class="' + classes.join(' ') + '" data-date="' + date.format() + '">' + | ||
10160 | date.date() + | ||
10161 | '</td>'; | ||
10162 | }, | ||
10163 | |||
10164 | |||
10165 | // Generates an HTML attribute string for setting the width of the week number column, if it is known | ||
10166 | weekNumberStyleAttr: function() { | ||
10167 | if (this.weekNumberWidth !== null) { | ||
10168 | return 'style="width:' + this.weekNumberWidth + 'px"'; | ||
10169 | } | ||
10170 | return ''; | ||
10171 | }, | ||
10172 | |||
10173 | |||
10174 | // Determines whether each row should have a constant height | ||
10175 | hasRigidRows: function() { | ||
10176 | var eventLimit = this.opt('eventLimit'); | ||
10177 | return eventLimit && typeof eventLimit !== 'number'; | ||
10178 | }, | ||
10179 | |||
10180 | |||
10181 | /* Dimensions | ||
10182 | ------------------------------------------------------------------------------------------------------------------*/ | ||
10183 | |||
10184 | |||
10185 | // Refreshes the horizontal dimensions of the view | ||
10186 | updateWidth: function() { | ||
10187 | if (this.weekNumbersVisible) { | ||
10188 | // Make sure all week number cells running down the side have the same width. | ||
10189 | // Record the width for cells created later. | ||
10190 | this.weekNumberWidth = matchCellWidths( | ||
10191 | this.el.find('.fc-week-number') | ||
10192 | ); | ||
10193 | } | ||
10194 | }, | ||
10195 | |||
10196 | |||
10197 | // Adjusts the vertical dimensions of the view to the specified values | ||
10198 | setHeight: function(totalHeight, isAuto) { | ||
10199 | var eventLimit = this.opt('eventLimit'); | ||
10200 | var scrollerHeight; | ||
10201 | |||
10202 | // reset all heights to be natural | ||
10203 | unsetScroller(this.scrollerEl); | ||
10204 | uncompensateScroll(this.headRowEl); | ||
10205 | |||
10206 | this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed | ||
10207 | |||
10208 | // is the event limit a constant level number? | ||
10209 | if (eventLimit && typeof eventLimit === 'number') { | ||
10210 | this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after | ||
10211 | } | ||
10212 | |||
10213 | scrollerHeight = this.computeScrollerHeight(totalHeight); | ||
10214 | this.setGridHeight(scrollerHeight, isAuto); | ||
10215 | |||
10216 | // is the event limit dynamically calculated? | ||
10217 | if (eventLimit && typeof eventLimit !== 'number') { | ||
10218 | this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set | ||
10219 | } | ||
10220 | |||
10221 | if (!isAuto && setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars? | ||
10222 | |||
10223 | compensateScroll(this.headRowEl, getScrollbarWidths(this.scrollerEl)); | ||
10224 | |||
10225 | // doing the scrollbar compensation might have created text overflow which created more height. redo | ||
10226 | scrollerHeight = this.computeScrollerHeight(totalHeight); | ||
10227 | this.scrollerEl.height(scrollerHeight); | ||
10228 | } | ||
10229 | }, | ||
10230 | |||
10231 | |||
10232 | // Sets the height of just the DayGrid component in this view | ||
10233 | setGridHeight: function(height, isAuto) { | ||
10234 | if (isAuto) { | ||
10235 | undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding | ||
10236 | } | ||
10237 | else { | ||
10238 | distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows | ||
10239 | } | ||
10240 | }, | ||
10241 | |||
10242 | |||
10243 | /* Events | ||
10244 | ------------------------------------------------------------------------------------------------------------------*/ | ||
10245 | |||
10246 | |||
10247 | // Renders the given events onto the view and populates the segments array | ||
10248 | renderEvents: function(events) { | ||
10249 | this.dayGrid.renderEvents(events); | ||
10250 | |||
10251 | this.updateHeight(); // must compensate for events that overflow the row | ||
10252 | }, | ||
10253 | |||
10254 | |||
10255 | // Retrieves all segment objects that are rendered in the view | ||
10256 | getEventSegs: function() { | ||
10257 | return this.dayGrid.getEventSegs(); | ||
10258 | }, | ||
10259 | |||
10260 | |||
10261 | // Unrenders all event elements and clears internal segment data | ||
10262 | destroyEvents: function() { | ||
10263 | this.dayGrid.destroyEvents(); | ||
10264 | |||
10265 | // we DON'T need to call updateHeight() because: | ||
10266 | // A) a renderEvents() call always happens after this, which will eventually call updateHeight() | ||
10267 | // B) in IE8, this causes a flash whenever events are rerendered | ||
10268 | }, | ||
10269 | |||
10270 | |||
10271 | /* Dragging (for both events and external elements) | ||
10272 | ------------------------------------------------------------------------------------------------------------------*/ | ||
10273 | |||
10274 | |||
10275 | // A returned value of `true` signals that a mock "helper" event has been rendered. | ||
10276 | renderDrag: function(dropLocation, seg) { | ||
10277 | return this.dayGrid.renderDrag(dropLocation, seg); | ||
10278 | }, | ||
10279 | |||
10280 | |||
10281 | destroyDrag: function() { | ||
10282 | this.dayGrid.destroyDrag(); | ||
10283 | }, | ||
10284 | |||
10285 | |||
10286 | /* Selection | ||
10287 | ------------------------------------------------------------------------------------------------------------------*/ | ||
10288 | |||
10289 | |||
10290 | // Renders a visual indication of a selection | ||
10291 | renderSelection: function(range) { | ||
10292 | this.dayGrid.renderSelection(range); | ||
10293 | }, | ||
10294 | |||
10295 | |||
10296 | // Unrenders a visual indications of a selection | ||
10297 | destroySelection: function() { | ||
10298 | this.dayGrid.destroySelection(); | ||
10299 | } | ||
10300 | |||
10301 | }); | ||
10302 | |||
10303 | ;; | ||
10304 | |||
10305 | /* A month view with day cells running in rows (one-per-week) and columns | ||
10306 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
10307 | |||
10308 | var MonthView = fcViews.month = BasicView.extend({ | ||
10309 | |||
10310 | // Produces information about what range to display | ||
10311 | computeRange: function(date) { | ||
10312 | var range = BasicView.prototype.computeRange.call(this, date); // get value from super-method | ||
10313 | var rowCnt; | ||
10314 | |||
10315 | // ensure 6 weeks | ||
10316 | if (this.isFixedWeeks()) { | ||
10317 | rowCnt = Math.ceil(range.end.diff(range.start, 'weeks', true)); // could be partial weeks due to hiddenDays | ||
10318 | range.end.add(6 - rowCnt, 'weeks'); | ||
10319 | } | ||
10320 | |||
10321 | return range; | ||
10322 | }, | ||
10323 | |||
10324 | |||
10325 | // Overrides the default BasicView behavior to have special multi-week auto-height logic | ||
10326 | setGridHeight: function(height, isAuto) { | ||
10327 | |||
10328 | isAuto = isAuto || this.opt('weekMode') === 'variable'; // LEGACY: weekMode is deprecated | ||
10329 | |||
10330 | // if auto, make the height of each row the height that it would be if there were 6 weeks | ||
10331 | if (isAuto) { | ||
10332 | height *= this.rowCnt / 6; | ||
10333 | } | ||
10334 | |||
10335 | distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows | ||
10336 | }, | ||
10337 | |||
10338 | |||
10339 | isFixedWeeks: function() { | ||
10340 | var weekMode = this.opt('weekMode'); // LEGACY: weekMode is deprecated | ||
10341 | if (weekMode) { | ||
10342 | return weekMode === 'fixed'; // if any other type of weekMode, assume NOT fixed | ||
10343 | } | ||
10344 | |||
10345 | return this.opt('fixedWeekCount'); | ||
10346 | } | ||
10347 | |||
10348 | }); | ||
10349 | |||
10350 | MonthView.duration = { months: 1 }; // important for prev/next | ||
10351 | |||
10352 | MonthView.defaults = { | ||
10353 | fixedWeekCount: true | ||
10354 | }; | ||
10355 | ;; | ||
10356 | |||
10357 | /* A week view with simple day cells running horizontally | ||
10358 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
10359 | |||
10360 | fcViews.basicWeek = { | ||
10361 | type: 'basic', | ||
10362 | duration: { weeks: 1 } | ||
10363 | }; | ||
10364 | ;; | ||
10365 | |||
10366 | /* A view with a single simple day cell | ||
10367 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
10368 | |||
10369 | fcViews.basicDay = { | ||
10370 | type: 'basic', | ||
10371 | duration: { days: 1 } | ||
10372 | }; | ||
10373 | ;; | ||
10374 | |||
10375 | /* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically. | ||
10376 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
10377 | // Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on). | ||
10378 | // Responsible for managing width/height. | ||
10379 | |||
10380 | var AGENDA_DEFAULTS = { | ||
10381 | allDaySlot: true, | ||
10382 | allDayText: 'all-day', | ||
10383 | scrollTime: '06:00:00', | ||
10384 | slotDuration: '00:30:00', | ||
10385 | minTime: '00:00:00', | ||
10386 | maxTime: '24:00:00', | ||
10387 | slotEventOverlap: true // a bad name. confused with overlap/constraint system | ||
10388 | }; | ||
10389 | |||
10390 | var AGENDA_ALL_DAY_EVENT_LIMIT = 5; | ||
10391 | |||
10392 | var AgendaView = fcViews.agenda = View.extend({ | ||
10393 | |||
10394 | timeGrid: null, // the main time-grid subcomponent of this view | ||
10395 | dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null | ||
10396 | |||
10397 | axisWidth: null, // the width of the time axis running down the side | ||
10398 | |||
10399 | noScrollRowEls: null, // set of fake row elements that must compensate when scrollerEl has scrollbars | ||
10400 | |||
10401 | // when the time-grid isn't tall enough to occupy the given height, we render an <hr> underneath | ||
10402 | bottomRuleEl: null, | ||
10403 | bottomRuleHeight: null, | ||
10404 | |||
10405 | |||
10406 | initialize: function() { | ||
10407 | this.timeGrid = new TimeGrid(this); | ||
10408 | |||
10409 | if (this.opt('allDaySlot')) { // should we display the "all-day" area? | ||
10410 | this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view | ||
10411 | |||
10412 | // the coordinate grid will be a combination of both subcomponents' grids | ||
10413 | this.coordMap = new ComboCoordMap([ | ||
10414 | this.dayGrid.coordMap, | ||
10415 | this.timeGrid.coordMap | ||
10416 | ]); | ||
10417 | } | ||
10418 | else { | ||
10419 | this.coordMap = this.timeGrid.coordMap; | ||
10420 | } | ||
10421 | }, | ||
10422 | |||
10423 | |||
10424 | /* Rendering | ||
10425 | ------------------------------------------------------------------------------------------------------------------*/ | ||
10426 | |||
10427 | |||
10428 | // Sets the display range and computes all necessary dates | ||
10429 | setRange: function(range) { | ||
10430 | View.prototype.setRange.call(this, range); // call the super-method | ||
10431 | |||
10432 | this.timeGrid.setRange(range); | ||
10433 | if (this.dayGrid) { | ||
10434 | this.dayGrid.setRange(range); | ||
10435 | } | ||
10436 | }, | ||
10437 | |||
10438 | |||
10439 | // Renders the view into `this.el`, which has already been assigned | ||
10440 | render: function() { | ||
10441 | |||
10442 | this.el.addClass('fc-agenda-view').html(this.renderHtml()); | ||
10443 | |||
10444 | // the element that wraps the time-grid that will probably scroll | ||
10445 | this.scrollerEl = this.el.find('.fc-time-grid-container'); | ||
10446 | this.timeGrid.coordMap.containerEl = this.scrollerEl; // don't accept clicks/etc outside of this | ||
10447 | |||
10448 | this.timeGrid.setElement(this.el.find('.fc-time-grid')); | ||
10449 | this.timeGrid.renderDates(); | ||
10450 | |||
10451 | // the <hr> that sometimes displays under the time-grid | ||
10452 | this.bottomRuleEl = $('<hr class="fc-divider ' + this.widgetHeaderClass + '"/>') | ||
10453 | .appendTo(this.timeGrid.el); // inject it into the time-grid | ||
10454 | |||
10455 | if (this.dayGrid) { | ||
10456 | this.dayGrid.setElement(this.el.find('.fc-day-grid')); | ||
10457 | this.dayGrid.renderDates(); | ||
10458 | |||
10459 | // have the day-grid extend it's coordinate area over the <hr> dividing the two grids | ||
10460 | this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight(); | ||
10461 | } | ||
10462 | |||
10463 | this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller | ||
10464 | }, | ||
10465 | |||
10466 | |||
10467 | // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering, | ||
10468 | // always completely kill each grid's rendering. | ||
10469 | destroy: function() { | ||
10470 | this.timeGrid.destroyDates(); | ||
10471 | this.timeGrid.removeElement(); | ||
10472 | |||
10473 | if (this.dayGrid) { | ||
10474 | this.dayGrid.destroyDates(); | ||
10475 | this.dayGrid.removeElement(); | ||
10476 | } | ||
10477 | }, | ||
10478 | |||
10479 | |||
10480 | renderBusinessHours: function() { | ||
10481 | this.timeGrid.renderBusinessHours(); | ||
10482 | |||
10483 | if (this.dayGrid) { | ||
10484 | this.dayGrid.renderBusinessHours(); | ||
10485 | } | ||
10486 | }, | ||
10487 | |||
10488 | |||
10489 | // Builds the HTML skeleton for the view. | ||
10490 | // The day-grid and time-grid components will render inside containers defined by this HTML. | ||
10491 | renderHtml: function() { | ||
10492 | return '' + | ||
10493 | '<table>' + | ||
10494 | '<thead class="fc-head">' + | ||
10495 | '<tr>' + | ||
10496 | '<td class="' + this.widgetHeaderClass + '">' + | ||
10497 | this.timeGrid.headHtml() + // render the day-of-week headers | ||
10498 | '</td>' + | ||
10499 | '</tr>' + | ||
10500 | '</thead>' + | ||
10501 | '<tbody class="fc-body">' + | ||
10502 | '<tr>' + | ||
10503 | '<td class="' + this.widgetContentClass + '">' + | ||
10504 | (this.dayGrid ? | ||
10505 | '<div class="fc-day-grid"/>' + | ||
10506 | '<hr class="fc-divider ' + this.widgetHeaderClass + '"/>' : | ||
10507 | '' | ||
10508 | ) + | ||
10509 | '<div class="fc-time-grid-container">' + | ||
10510 | '<div class="fc-time-grid"/>' + | ||
10511 | '</div>' + | ||
10512 | '</td>' + | ||
10513 | '</tr>' + | ||
10514 | '</tbody>' + | ||
10515 | '</table>'; | ||
10516 | }, | ||
10517 | |||
10518 | |||
10519 | // Generates the HTML that will go before the day-of week header cells. | ||
10520 | // Queried by the TimeGrid subcomponent when generating rows. Ordering depends on isRTL. | ||
10521 | headIntroHtml: function() { | ||
10522 | var date; | ||
10523 | var weekText; | ||
10524 | |||
10525 | if (this.opt('weekNumbers')) { | ||
10526 | date = this.timeGrid.getCell(0).start; | ||
10527 | weekText = date.format(this.opt('smallWeekFormat')); | ||
10528 | |||
10529 | return '' + | ||
10530 | '<th class="fc-axis fc-week-number ' + this.widgetHeaderClass + '" ' + this.axisStyleAttr() + '>' + | ||
10531 | '<span>' + // needed for matchCellWidths | ||
10532 | htmlEscape(weekText) + | ||
10533 | '</span>' + | ||
10534 | '</th>'; | ||
10535 | } | ||
10536 | else { | ||
10537 | return '<th class="fc-axis ' + this.widgetHeaderClass + '" ' + this.axisStyleAttr() + '></th>'; | ||
10538 | } | ||
10539 | }, | ||
10540 | |||
10541 | |||
10542 | // Generates the HTML that goes before the all-day cells. | ||
10543 | // Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL. | ||
10544 | dayIntroHtml: function() { | ||
10545 | return '' + | ||
10546 | '<td class="fc-axis ' + this.widgetContentClass + '" ' + this.axisStyleAttr() + '>' + | ||
10547 | '<span>' + // needed for matchCellWidths | ||
10548 | (this.opt('allDayHtml') || htmlEscape(this.opt('allDayText'))) + | ||
10549 | '</span>' + | ||
10550 | '</td>'; | ||
10551 | }, | ||
10552 | |||
10553 | |||
10554 | // Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column. | ||
10555 | slotBgIntroHtml: function() { | ||
10556 | return '<td class="fc-axis ' + this.widgetContentClass + '" ' + this.axisStyleAttr() + '></td>'; | ||
10557 | }, | ||
10558 | |||
10559 | |||
10560 | // Generates the HTML that goes before all other types of cells. | ||
10561 | // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid. | ||
10562 | // Queried by the TimeGrid and DayGrid subcomponents when generating rows. Ordering depends on isRTL. | ||
10563 | introHtml: function() { | ||
10564 | return '<td class="fc-axis" ' + this.axisStyleAttr() + '></td>'; | ||
10565 | }, | ||
10566 | |||
10567 | |||
10568 | // Generates an HTML attribute string for setting the width of the axis, if it is known | ||
10569 | axisStyleAttr: function() { | ||
10570 | if (this.axisWidth !== null) { | ||
10571 | return 'style="width:' + this.axisWidth + 'px"'; | ||
10572 | } | ||
10573 | return ''; | ||
10574 | }, | ||
10575 | |||
10576 | |||
10577 | /* Dimensions | ||
10578 | ------------------------------------------------------------------------------------------------------------------*/ | ||
10579 | |||
10580 | |||
10581 | updateSize: function(isResize) { | ||
10582 | this.timeGrid.updateSize(isResize); | ||
10583 | |||
10584 | View.prototype.updateSize.call(this, isResize); // call the super-method | ||
10585 | }, | ||
10586 | |||
10587 | |||
10588 | // Refreshes the horizontal dimensions of the view | ||
10589 | updateWidth: function() { | ||
10590 | // make all axis cells line up, and record the width so newly created axis cells will have it | ||
10591 | this.axisWidth = matchCellWidths(this.el.find('.fc-axis')); | ||
10592 | }, | ||
10593 | |||
10594 | |||
10595 | // Adjusts the vertical dimensions of the view to the specified values | ||
10596 | setHeight: function(totalHeight, isAuto) { | ||
10597 | var eventLimit; | ||
10598 | var scrollerHeight; | ||
10599 | |||
10600 | if (this.bottomRuleHeight === null) { | ||
10601 | // calculate the height of the rule the very first time | ||
10602 | this.bottomRuleHeight = this.bottomRuleEl.outerHeight(); | ||
10603 | } | ||
10604 | this.bottomRuleEl.hide(); // .show() will be called later if this <hr> is necessary | ||
10605 | |||
10606 | // reset all dimensions back to the original state | ||
10607 | this.scrollerEl.css('overflow', ''); | ||
10608 | unsetScroller(this.scrollerEl); | ||
10609 | uncompensateScroll(this.noScrollRowEls); | ||
10610 | |||
10611 | // limit number of events in the all-day area | ||
10612 | if (this.dayGrid) { | ||
10613 | this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed | ||
10614 | |||
10615 | eventLimit = this.opt('eventLimit'); | ||
10616 | if (eventLimit && typeof eventLimit !== 'number') { | ||
10617 | eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number | ||
10618 | } | ||
10619 | if (eventLimit) { | ||
10620 | this.dayGrid.limitRows(eventLimit); | ||
10621 | } | ||
10622 | } | ||
10623 | |||
10624 | if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height? | ||
10625 | |||
10626 | scrollerHeight = this.computeScrollerHeight(totalHeight); | ||
10627 | if (setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars? | ||
10628 | |||
10629 | // make the all-day and header rows lines up | ||
10630 | compensateScroll(this.noScrollRowEls, getScrollbarWidths(this.scrollerEl)); | ||
10631 | |||
10632 | // the scrollbar compensation might have changed text flow, which might affect height, so recalculate | ||
10633 | // and reapply the desired height to the scroller. | ||
10634 | scrollerHeight = this.computeScrollerHeight(totalHeight); | ||
10635 | this.scrollerEl.height(scrollerHeight); | ||
10636 | } | ||
10637 | else { // no scrollbars | ||
10638 | // still, force a height and display the bottom rule (marks the end of day) | ||
10639 | this.scrollerEl.height(scrollerHeight).css('overflow', 'hidden'); // in case <hr> goes outside | ||
10640 | this.bottomRuleEl.show(); | ||
10641 | } | ||
10642 | } | ||
10643 | }, | ||
10644 | |||
10645 | |||
10646 | // Computes the initial pre-configured scroll state prior to allowing the user to change it | ||
10647 | computeInitialScroll: function() { | ||
10648 | var scrollTime = moment.duration(this.opt('scrollTime')); | ||
10649 | var top = this.timeGrid.computeTimeTop(scrollTime); | ||
10650 | |||
10651 | // zoom can give weird floating-point values. rather scroll a little bit further | ||
10652 | top = Math.ceil(top); | ||
10653 | |||
10654 | if (top) { | ||
10655 | top++; // to overcome top border that slots beyond the first have. looks better | ||
10656 | } | ||
10657 | |||
10658 | return top; | ||
10659 | }, | ||
10660 | |||
10661 | |||
10662 | /* Events | ||
10663 | ------------------------------------------------------------------------------------------------------------------*/ | ||
10664 | |||
10665 | |||
10666 | // Renders events onto the view and populates the View's segment array | ||
10667 | renderEvents: function(events) { | ||
10668 | var dayEvents = []; | ||
10669 | var timedEvents = []; | ||
10670 | var daySegs = []; | ||
10671 | var timedSegs; | ||
10672 | var i; | ||
10673 | |||
10674 | // separate the events into all-day and timed | ||
10675 | for (i = 0; i < events.length; i++) { | ||
10676 | if (events[i].allDay) { | ||
10677 | dayEvents.push(events[i]); | ||
10678 | } | ||
10679 | else { | ||
10680 | timedEvents.push(events[i]); | ||
10681 | } | ||
10682 | } | ||
10683 | |||
10684 | // render the events in the subcomponents | ||
10685 | timedSegs = this.timeGrid.renderEvents(timedEvents); | ||
10686 | if (this.dayGrid) { | ||
10687 | daySegs = this.dayGrid.renderEvents(dayEvents); | ||
10688 | } | ||
10689 | |||
10690 | // the all-day area is flexible and might have a lot of events, so shift the height | ||
10691 | this.updateHeight(); | ||
10692 | }, | ||
10693 | |||
10694 | |||
10695 | // Retrieves all segment objects that are rendered in the view | ||
10696 | getEventSegs: function() { | ||
10697 | return this.timeGrid.getEventSegs().concat( | ||
10698 | this.dayGrid ? this.dayGrid.getEventSegs() : [] | ||
10699 | ); | ||
10700 | }, | ||
10701 | |||
10702 | |||
10703 | // Unrenders all event elements and clears internal segment data | ||
10704 | destroyEvents: function() { | ||
10705 | |||
10706 | // destroy the events in the subcomponents | ||
10707 | this.timeGrid.destroyEvents(); | ||
10708 | if (this.dayGrid) { | ||
10709 | this.dayGrid.destroyEvents(); | ||
10710 | } | ||
10711 | |||
10712 | // we DON'T need to call updateHeight() because: | ||
10713 | // A) a renderEvents() call always happens after this, which will eventually call updateHeight() | ||
10714 | // B) in IE8, this causes a flash whenever events are rerendered | ||
10715 | }, | ||
10716 | |||
10717 | |||
10718 | /* Dragging (for events and external elements) | ||
10719 | ------------------------------------------------------------------------------------------------------------------*/ | ||
10720 | |||
10721 | |||
10722 | // A returned value of `true` signals that a mock "helper" event has been rendered. | ||
10723 | renderDrag: function(dropLocation, seg) { | ||
10724 | if (dropLocation.start.hasTime()) { | ||
10725 | return this.timeGrid.renderDrag(dropLocation, seg); | ||
10726 | } | ||
10727 | else if (this.dayGrid) { | ||
10728 | return this.dayGrid.renderDrag(dropLocation, seg); | ||
10729 | } | ||
10730 | }, | ||
10731 | |||
10732 | |||
10733 | destroyDrag: function() { | ||
10734 | this.timeGrid.destroyDrag(); | ||
10735 | if (this.dayGrid) { | ||
10736 | this.dayGrid.destroyDrag(); | ||
10737 | } | ||
10738 | }, | ||
10739 | |||
10740 | |||
10741 | /* Selection | ||
10742 | ------------------------------------------------------------------------------------------------------------------*/ | ||
10743 | |||
10744 | |||
10745 | // Renders a visual indication of a selection | ||
10746 | renderSelection: function(range) { | ||
10747 | if (range.start.hasTime() || range.end.hasTime()) { | ||
10748 | this.timeGrid.renderSelection(range); | ||
10749 | } | ||
10750 | else if (this.dayGrid) { | ||
10751 | this.dayGrid.renderSelection(range); | ||
10752 | } | ||
10753 | }, | ||
10754 | |||
10755 | |||
10756 | // Unrenders a visual indications of a selection | ||
10757 | destroySelection: function() { | ||
10758 | this.timeGrid.destroySelection(); | ||
10759 | if (this.dayGrid) { | ||
10760 | this.dayGrid.destroySelection(); | ||
10761 | } | ||
10762 | } | ||
10763 | |||
10764 | }); | ||
10765 | |||
10766 | AgendaView.defaults = AGENDA_DEFAULTS; | ||
10767 | |||
10768 | ;; | ||
10769 | |||
10770 | /* A week view with an all-day cell area at the top, and a time grid below | ||
10771 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
10772 | |||
10773 | fcViews.agendaWeek = { | ||
10774 | type: 'agenda', | ||
10775 | duration: { weeks: 1 } | ||
10776 | }; | ||
10777 | ;; | ||
10778 | |||
10779 | /* A day view with an all-day cell area at the top, and a time grid below | ||
10780 | ----------------------------------------------------------------------------------------------------------------------*/ | ||
10781 | |||
10782 | fcViews.agendaDay = { | ||
10783 | type: 'agenda', | ||
10784 | duration: { days: 1 } |
app/partials/scheduler/scheduler.controller.js
1 | 'use strict'; | 1 | 'use strict'; |
2 | 2 | ||
3 | //Load controller | 3 | //Load controller |
4 | angular.module('acufuel') | 4 | angular.module('acufuel') |
5 | 5 | ||
6 | .controller('schedulerController', ['$scope', function($scope) { | 6 | .controller('schedulerController', ['$scope','$compile', 'uiCalendarConfig', function($scope, $compile, uiCalendarConfig) { |
7 | 7 | ||
8 | $scope.test = "Testing..."; | 8 | $scope.test = "Testing..."; |
9 | |||
10 | var date = new Date(); | ||
11 | var d = date.getDate(); | ||
12 | var m = date.getMonth(); | ||
13 | var y = date.getFullYear(); | ||
14 | |||
9 | 15 | ||
10 | 16 | $scope.eventList=[ | |
11 | /*$('#calendar').fullCalendar({ | 17 | {title:'Event 1'}, |
12 | header: { | 18 | {title:'Event 2'}, |
13 | left: 'prev,next today', | 19 | {title:'Event 3'}, |
14 | center: 'title', | 20 | {title:'Event 4'} |
15 | right: 'month,agendaWeek,agendaDay,listWeek' | 21 | ]; |
16 | }, | 22 | |
17 | defaultDate: '2017-05-12', | 23 | $scope.eventSources=[]; |
18 | navLinks: true, // can click day/week names to navigate views | 24 | |
19 | editable: true, | 25 | $scope.events = [ |
20 | eventLimit: true, // allow "more" link when too many events | 26 | {title: 'All Day Event', start: new Date(y, m, 1)}, |
21 | events: [ | 27 | {title: 'Birthday Party', start: new Date(y, m, d + 1, 19, 0), end: new Date(y, m, d + 1, 22, 30), allDay: false}, |
22 | { | 28 | {title: 'Click for Google', start: new Date(y, m, 28), end: new Date(y, m, 29)} |
23 | title: 'All Day Event', | 29 | ]; |
24 | start: '2017-05-01' | 30 | |
25 | }, | 31 | $scope.uiConfig = { |
26 | { | 32 | calendar:{ |
27 | title: 'Long Event', | 33 | height: 450, |
28 | start: '2017-05-07', | 34 | editable: true, |
29 | end: '2017-05-10' | 35 | droppable: true, |
30 | }, | 36 | drop: function (date, allDay, jsEvent, ui) { |
31 | { | 37 | console.log('Here ,but where is the object?'); |
32 | id: 999, | 38 | }, |
33 | title: 'Repeating Event', | 39 | header:{ |
34 | start: '2017-05-09T16:00:00' | 40 | left: 'title', |
35 | }, | 41 | center: '', |
36 | { | 42 | right: 'today prev,next' |
37 | id: 999, | 43 | }, |
38 | title: 'Repeating Event', | 44 | eventResize: true, |
39 | start: '2017-05-16T16:00:00' | ||
40 | }, | ||
41 | { | ||
42 | title: 'Conference', | ||
43 | start: '2017-05-11', | ||
44 | end: '2017-05-13' | ||
45 | }, | ||
46 | { | ||
47 | title: 'Meeting', | ||
48 | start: '2017-05-12T10:30:00', | ||
49 | end: '2017-05-12T12:30:00' | ||
50 | }, | ||
51 | { | ||
52 | title: 'Lunch', | ||
53 | start: '2017-05-12T12:00:00' | ||
54 | }, | ||
55 | { | ||
56 | title: 'Meeting', | ||
57 | start: '2017-05-12T14:30:00' | ||
58 | }, | ||
59 | { | ||
60 | title: 'Happy Hour', | ||
61 | start: '2017-05-12T17:30:00' | ||
62 | }, | ||
63 | { | ||
64 | title: 'Dinner', | ||
65 | start: '2017-05-12T20:00:00' | ||
66 | }, | ||
67 | { | ||
68 | title: 'Birthday Party', | ||
69 | start: '2017-05-13T07:00:00' | ||
70 | }, | ||
71 | { | ||
72 | title: 'Click for Google', | ||
73 | start: '2017-05-28' | ||
74 | } | 45 | } |
75 | ] | 46 | }; |
76 | }); | ||
77 |
app/partials/scheduler/scheduler.html
1 | <style type="text/css"> | 1 | <style type="text/css"> |
2 | .newUlView { | 2 | .newUlView { |
3 | padding: 5px; | 3 | padding: 5px; |
4 | margin: 3px; | 4 | margin: 3px; |
5 | } | 5 | } |
6 | .subnavbar .mainnav > li:nth-child(4) > a{ | 6 | .subnavbar .mainnav > li:nth-child(4) > a{ |
7 | color: #ff9900; | 7 | color: #ff9900; |
8 | } | 8 | } |
9 | .fc button, .fc-button-group, .fc-time-grid .fc-event .fc-time span{ | 9 | .fc button, .fc-button-group, .fc-time-grid .fc-event .fc-time span{ |
10 | display: block; | 10 | display: block; |
11 | } | 11 | } |
12 | .fc-state-default{ | 12 | .fc-state-default{ |
13 | background-color: #fff; | 13 | background-color: #fff; |
14 | background-image: none; | 14 | background-image: none; |
15 | } | 15 | } |
16 | .fc-state-active, .fc-state-down{ | 16 | .fc-state-active, .fc-state-down{ |
17 | box-shadow: none; | 17 | box-shadow: none; |
18 | color: #fff; | 18 | color: #fff; |
19 | background-color: #ff9900; | 19 | background-color: #ff9900; |
20 | } | 20 | } |
21 | 21 | ||
22 | 22 | ||
23 | /*#wrap { | 23 | /*#wrap { |
24 | width: 1100px; | 24 | width: 1100px; |
25 | margin: 0 auto; | 25 | margin: 0 auto; |
26 | } | 26 | } |
27 | 27 | ||
28 | #external-events { | 28 | #external-events { |
29 | float: left; | 29 | float: left; |
30 | width: 150px; | 30 | width: 150px; |
31 | padding: 0 10px; | 31 | padding: 0 10px; |
32 | border: 1px solid #ccc; | 32 | border: 1px solid #ccc; |
33 | background: #eee; | 33 | background: #eee; |
34 | text-align: left; | 34 | text-align: left; |
35 | } | 35 | } |
36 | 36 | ||
37 | #external-events h4 { | 37 | #external-events h4 { |
38 | font-size: 16px; | 38 | font-size: 16px; |
39 | margin-top: 0; | 39 | margin-top: 0; |
40 | padding-top: 1em; | 40 | padding-top: 1em; |
41 | } | 41 | } |
42 | 42 | ||
43 | #external-events .fc-event { | 43 | #external-events .fc-event { |
44 | margin: 10px 0; | 44 | margin: 10px 0; |
45 | cursor: pointer; | 45 | cursor: pointer; |
46 | } | 46 | } |
47 | 47 | ||
48 | #external-events p { | 48 | #external-events p { |
49 | margin: 1.5em 0; | 49 | margin: 1.5em 0; |
50 | font-size: 11px; | 50 | font-size: 11px; |
51 | color: #666; | 51 | color: #666; |
52 | } | 52 | } |
53 | 53 | ||
54 | #external-events p input { | 54 | #external-events p input { |
55 | margin: 0; | 55 | margin: 0; |
56 | vertical-align: middle; | 56 | vertical-align: middle; |
57 | } | 57 | } |
58 | 58 | ||
59 | #calendar { | 59 | #calendar { |
60 | float: right; | 60 | float: right; |
61 | width: 900px; | 61 | width: 900px; |
62 | }*/ | 62 | }*/ |
63 | </style> | 63 | </style> |
64 | <div class="main"> | 64 | <div class="main"> |
65 | <div class="container"> | 65 | <div class="container"> |
66 | <div class="row"> | 66 | <div class="row"> |
67 | <div class="col-xs-12"> | 67 | <div class="col-xs-12"> |
68 | <!-- <div id='wrap'> | 68 | <div id='wrap'> |
69 | 69 | ||
70 | <div id='external-events'> | 70 | <div id='external-events' class="col-xs-12 col-md-3"> |
71 | <h4>Draggable Events</h4> | 71 | <h4>Draggable Events</h4> |
72 | <div class='fc-event'>My Event 1</div> | 72 | <label ng-repeat="item in eventList" style="width: 100%;"> |
73 | <div class='fc-event'>My Event 2</div> | 73 | <div class="fc-event" data-drag="true" data-jqyoui-options="{revert: 'invalid'}" jqyoui-draggable="{index: {{$index}},placeholder:true,animate:true}"> |
74 | <div class='fc-event'>My Event 3</div> | 74 | {{item.title}} |
75 | <div class='fc-event'>My Event 4</div> | 75 | </div> |
76 | <div class='fc-event'>My Event 5</div> | 76 | </label> |
77 | <p> | 77 | <!-- <p> |
78 | <input type='checkbox' id='drop-remove' /> | 78 | <input type='checkbox' id='drop-remove' /> |
79 | <label for='drop-remove'>remove after drop</label> | 79 | <label for='drop-remove'>remove after drop</label> |
80 | </p> | 80 | </p> --> |
81 | </div> | 81 | </div> |
82 | 82 | ||
83 | <div id='calendar'></div> | 83 | <div ui-calendar="uiConfig.calendar" ng-model="eventSources" class="col-xs-12 col-md-9" data-drop="true" jqyoui-droppable="{multiple:true, onDrop: 'addEvent($index)'}"></div> |
84 | 84 | ||
85 | <div style='clear:both'></div> | 85 | <div style='clear:both'></div> |
86 | 86 | ||
87 | </div> --> | 87 | </div> |
88 | |||
89 | |||
88 | 90 | ||
89 | |||
90 | </div> | 91 | </div> |
91 | 92 | ||
92 | </div> | 93 | </div> |
94 | <div> | ||
95 | </div> | ||
93 | <!-- /row --> | 96 | <!-- /row --> |
94 | </div> | 97 | </div> |
95 | <!-- /container --> | 98 | <!-- /container --> |
96 | </div> | 99 | </div> |
app/partials/updateFuelManager/updateFuelManager.controller.js
1 | 1 | ||
2 | 'use strict'; | 2 | 'use strict'; |
3 | 3 | ||
4 | //Load controller | 4 | //Load controller |
5 | angular.module('acufuel') | 5 | angular.module('acufuel') |
6 | 6 | ||
7 | 7 | ||
8 | .controller('updateFuelManagerController', ['$scope','$uibModal', 'updateFuelManagerService', function($scope , $uibModal, updateFuelManagerService) { | 8 | .controller('updateFuelManagerController', ['$scope','$uibModal', 'updateFuelManagerService', function($scope , $uibModal, updateFuelManagerService) { |
9 | 9 | ||
10 | 10 | ||
11 | 11 | ||
12 | $scope.showLoader = true; | 12 | $scope.showLoader = true; |
13 | $scope.yes = function(data){ | 13 | $scope.yes = function(data){ |
14 | console.log('========'); | 14 | console.log('========'); |
15 | console.log('value', data); | 15 | console.log('value', data); |
16 | $uibModal.yes({ | 16 | $uibModal.yes({ |
17 | templateUrl: 'partials/pricingcontact/pricingcontact.html', | 17 | templateUrl: 'partials/pricingcontact/pricingcontact.html', |
18 | backdrop: true, | 18 | backdrop: true, |
19 | scope: $scope, | 19 | scope: $scope, |
20 | }) | 20 | }) |
21 | } | 21 | } |
22 | 22 | ||
23 | $scope.options = { | 23 | $scope.options = { |
24 | language: 'en', | 24 | language: 'en', |
25 | allowedContent: true, | 25 | allowedContent: true, |
26 | entities: false | 26 | entities: false |
27 | }; | 27 | }; |
28 | 28 | ||
29 | // Called when the editor is completely ready. | 29 | // Called when the editor is completely ready. |
30 | $scope.onReady = function () { | 30 | $scope.onReady = function () { |
31 | // ... | 31 | // ... |
32 | }; | 32 | }; |
33 | 33 | ||
34 | $scope.userProfileId = JSON.parse(localStorage.getItem('userProfileId')); | 34 | $scope.userProfileId = JSON.parse(localStorage.getItem('userProfileId')); |
35 | 35 | ||
36 | updateFuelManagerService.getATypeJets($scope.userProfileId).then(function(result) { | 36 | updateFuelManagerService.getATypeJets($scope.userProfileId).then(function(result) { |
37 | $scope.aTypeJets = result; | 37 | $scope.aTypeJets = result; |
38 | $scope.showLoader = false; | 38 | $scope.showLoader = false; |
39 | }) | 39 | }) |
40 | updateFuelManagerService.getVTypeJets($scope.userProfileId).then(function(result) { | 40 | updateFuelManagerService.getVTypeJets($scope.userProfileId).then(function(result) { |
41 | $scope.vTypeJets = result; | 41 | $scope.vTypeJets = result; |
42 | $scope.showLoader = false; | 42 | $scope.showLoader = false; |
43 | }) | 43 | }) |
44 | $scope.toggleJestAccordian = function(id, index){ | 44 | $scope.toggleJestAccordian = function(id, index){ |
45 | $scope.showLoader = true; | 45 | $scope.showLoader = true; |
46 | $('.'+id).slideDown(); | 46 | $('.'+id).slideDown(); |
47 | $('#'+id).addClass('customActive'); | 47 | $('#'+id).addClass('customActive'); |
48 | $('#'+id+' select, #'+id+' input').prop("disabled", false); | 48 | $('#'+id+' select, #'+id+' input').prop("disabled", false); |
49 | $('#'+id+' .btn-success, #'+id+' .btn-danger').css('display', 'inline-block'); | 49 | $('#'+id+' .btn-success, #'+id+' .btn-danger').css('display', 'inline-block'); |
50 | $('#'+id+' .btn-default').css('display', 'none'); | 50 | $('#'+id+' .btn-default').css('display', 'none'); |
51 | $('#'+id+' .btn-primary').css('display', 'none'); | 51 | $('#'+id+' .btn-primary').css('display', 'none'); |
52 | 52 | ||
53 | updateFuelManagerService.getJetTiers(id).then(function(tiers) { | 53 | updateFuelManagerService.getJetTiers(id).then(function(tiers) { |
54 | $scope.aTypeJets[index].tierList = tiers; | 54 | $scope.aTypeJets[index].tierList = tiers; |
55 | $scope.showLoader = false; | 55 | $scope.showLoader = false; |
56 | }) | 56 | }) |
57 | } | 57 | } |
58 | $scope.toggleVtypeJestAccordian = function(id, index){ | 58 | $scope.toggleVtypeJestAccordian = function(id, index){ |
59 | $scope.showLoader = true; | 59 | $scope.showLoader = true; |
60 | $('.'+id).slideDown(); | 60 | $('.'+id).slideDown(); |
61 | $('#'+id).addClass('customActive'); | 61 | $('#'+id).addClass('customActive'); |
62 | $('#'+id+' select, #'+id+' input').prop("disabled", false); | 62 | $('#'+id+' select, #'+id+' input').prop("disabled", false); |
63 | $('#'+id+' .btn-success, #'+id+' .btn-danger').css('display', 'inline-block'); | 63 | $('#'+id+' .btn-success, #'+id+' .btn-danger').css('display', 'inline-block'); |
64 | $('#'+id+' .btn-default').css('display', 'none'); | 64 | $('#'+id+' .btn-default').css('display', 'none'); |
65 | $('#'+id+' .btn-primary').css('display', 'none'); | 65 | $('#'+id+' .btn-primary').css('display', 'none'); |
66 | 66 | ||
67 | updateFuelManagerService.getJetTiers(id).then(function(tiers) { | 67 | updateFuelManagerService.getJetTiers(id).then(function(tiers) { |
68 | $scope.vTypeJets[index].tierList = tiers; | 68 | $scope.vTypeJets[index].tierList = tiers; |
69 | $scope.showLoader = false; | 69 | $scope.showLoader = false; |
70 | }) | 70 | }) |
71 | } | 71 | } |
72 | //$scope.trData = {}; | 72 | //$scope.trData = {}; |
73 | $scope.addNewTier = function(id, trData, index){ | 73 | $scope.addNewTier = function(id, trData, index){ |
74 | $scope.showLoader = true; | 74 | $scope.showLoader = true; |
75 | $scope.tr = {}; | 75 | $scope.tr = {}; |
76 | $scope.tr[index] = {}; | 76 | $scope.tr[index] = {}; |
77 | $scope.tr[index].minTierBreak = trData[index].minTierBreak; | 77 | $scope.tr[index].minTierBreak = trData[index].minTierBreak; |
78 | $scope.tr[index].maxTierBreak = trData[index].maxTierBreak; | 78 | $scope.tr[index].maxTierBreak = trData[index].maxTierBreak; |
79 | $scope.tr[index].margin = trData[index].margin; | 79 | $scope.tr[index].margin = trData[index].margin; |
80 | $scope.tr[index].marginTotal = '1.00'; | 80 | $scope.tr[index].marginTotal = '1.00'; |
81 | $scope.tr[index].marginTemplateId = id; | 81 | $scope.tr[index].marginTemplateId = id; |
82 | 82 | ||
83 | var tierData = 'minTierBreak='+$scope.tr[index].minTierBreak+'&maxTierBreak='+$scope.tr[index].maxTierBreak+'&margin='+$scope.tr[index].margin+ | 83 | var tierData = 'minTierBreak='+$scope.tr[index].minTierBreak+'&maxTierBreak='+$scope.tr[index].maxTierBreak+'&margin='+$scope.tr[index].margin+ |
84 | '&marginTotal='+$scope.tr[index].marginTotal+'&marginTemplateId='+$scope.tr[index].marginTemplateId; | 84 | '&marginTotal='+$scope.tr[index].marginTotal+'&marginTemplateId='+$scope.tr[index].marginTemplateId; |
85 | 85 | ||
86 | updateFuelManagerService.addNewTier(tierData).then(function(result) { | 86 | updateFuelManagerService.addNewTier(tierData).then(function(result) { |
87 | toastr.success('Successfully Added', { | 87 | toastr.success('Successfully Added', { |
88 | closeButton: true | 88 | closeButton: true |
89 | }) | 89 | }) |
90 | trData[index].minTierBreak = ''; | 90 | trData[index].minTierBreak = ''; |
91 | trData[index].maxTierBreak = ''; | 91 | trData[index].maxTierBreak = ''; |
92 | trData[index].margin = ''; | 92 | trData[index].margin = ''; |
93 | 93 | ||
94 | updateFuelManagerService.getJetTiers(id).then(function(tiers) { | 94 | updateFuelManagerService.getJetTiers(id).then(function(tiers) { |
95 | $scope.aTypeJets[index].tierList = tiers; | 95 | $scope.aTypeJets[index].tierList = tiers; |
96 | $scope.showLoader = false; | 96 | $scope.showLoader = false; |
97 | }) | 97 | }) |
98 | }) | 98 | }) |
99 | } | 99 | } |
100 | 100 | ||
101 | $scope.addNewVtypeTier = function(id, vtrData, index){ | 101 | $scope.addNewVtypeTier = function(id, vtrData, index){ |
102 | $scope.showLoader = true; | 102 | $scope.showLoader = true; |
103 | $scope.tr = {}; | 103 | $scope.tr = {}; |
104 | $scope.tr[index] = {}; | 104 | $scope.tr[index] = {}; |
105 | $scope.tr[index].minTierBreak = vtrData[index].minTierBreak; | 105 | $scope.tr[index].minTierBreak = vtrData[index].minTierBreak; |
106 | $scope.tr[index].maxTierBreak = vtrData[index].maxTierBreak; | 106 | $scope.tr[index].maxTierBreak = vtrData[index].maxTierBreak; |
107 | $scope.tr[index].margin = vtrData[index].margin; | 107 | $scope.tr[index].margin = vtrData[index].margin; |
108 | $scope.tr[index].marginTotal = '1.00'; | 108 | $scope.tr[index].marginTotal = '1.00'; |
109 | $scope.tr[index].marginTemplateId = id; | 109 | $scope.tr[index].marginTemplateId = id; |
110 | 110 | ||
111 | var tierData = 'minTierBreak='+$scope.tr[index].minTierBreak+'&maxTierBreak='+$scope.tr[index].maxTierBreak+'&margin='+$scope.tr[index].margin+ | 111 | var tierData = 'minTierBreak='+$scope.tr[index].minTierBreak+'&maxTierBreak='+$scope.tr[index].maxTierBreak+'&margin='+$scope.tr[index].margin+ |
112 | '&marginTotal='+$scope.tr[index].marginTotal+'&marginTemplateId='+$scope.tr[index].marginTemplateId; | 112 | '&marginTotal='+$scope.tr[index].marginTotal+'&marginTemplateId='+$scope.tr[index].marginTemplateId; |
113 | 113 | ||
114 | updateFuelManagerService.addNewTier(tierData).then(function(result) { | 114 | updateFuelManagerService.addNewTier(tierData).then(function(result) { |
115 | toastr.success('Successfully Added', { | 115 | toastr.success('Successfully Added', { |
116 | closeButton: true | 116 | closeButton: true |
117 | }) | 117 | }) |
118 | vtrData[index].minTierBreak = ''; | 118 | vtrData[index].minTierBreak = ''; |
119 | vtrData[index].maxTierBreak = ''; | 119 | vtrData[index].maxTierBreak = ''; |
120 | vtrData[index].margin = ''; | 120 | vtrData[index].margin = ''; |
121 | updateFuelManagerService.getJetTiers(id).then(function(tiers) { | 121 | updateFuelManagerService.getJetTiers(id).then(function(tiers) { |
122 | $scope.vTypeJets[index].tierList = tiers; | 122 | $scope.vTypeJets[index].tierList = tiers; |
123 | $scope.showLoader = false; | 123 | $scope.showLoader = false; |
124 | }) | 124 | }) |
125 | }) | 125 | }) |
126 | } | 126 | } |
127 | 127 | ||
128 | $scope.editTier = function(tier, index){ | 128 | $scope.editTier = function(tier, index){ |
129 | $scope.showLoader = true; | 129 | $scope.showLoader = true; |
130 | var editTierData = 'minTierBreak='+tier.minTierBreak+'&maxTierBreak='+tier.maxTierBreak+'&margin='+tier.margin+ | 130 | var editTierData = 'minTierBreak='+tier.minTierBreak+'&maxTierBreak='+tier.maxTierBreak+'&margin='+tier.margin+ |
131 | '&marginTotal='+tier.marginTotal+'&marginTemplateId='+tier.marginTemplate.id+'&marginId='+tier.id; | 131 | '&marginTotal='+tier.marginTotal+'&marginTemplateId='+tier.marginTemplate.id+'&marginId='+tier.id; |
132 | 132 | ||
133 | updateFuelManagerService.editTier(editTierData).then(function(result) { | 133 | updateFuelManagerService.editTier(editTierData).then(function(result) { |
134 | toastr.success('Successfully Updated', { | 134 | toastr.success('Successfully Updated', { |
135 | closeButton: true | 135 | closeButton: true |
136 | }) | 136 | }) |
137 | updateFuelManagerService.getJetTiers(tier.marginTemplate.id).then(function(tiers) { | 137 | updateFuelManagerService.getJetTiers(tier.marginTemplate.id).then(function(tiers) { |
138 | $scope.aTypeJets[index].tierList = tiers; | 138 | $scope.aTypeJets[index].tierList = tiers; |
139 | $scope.showLoader = false; | 139 | $scope.showLoader = false; |
140 | }) | 140 | }) |
141 | }) | 141 | }) |
142 | 142 | ||
143 | } | 143 | } |
144 | 144 | ||
145 | $scope.editVtypeTier = function(tier, index){ | 145 | $scope.editVtypeTier = function(tier, index){ |
146 | $scope.showLoader = true; | 146 | $scope.showLoader = true; |
147 | var editTierData = 'minTierBreak='+tier.minTierBreak+'&maxTierBreak='+tier.maxTierBreak+'&margin='+tier.margin+ | 147 | var editTierData = 'minTierBreak='+tier.minTierBreak+'&maxTierBreak='+tier.maxTierBreak+'&margin='+tier.margin+ |
148 | '&marginTotal='+tier.marginTotal+'&marginTemplateId='+tier.marginTemplate.id+'&marginId='+tier.id; | 148 | '&marginTotal='+tier.marginTotal+'&marginTemplateId='+tier.marginTemplate.id+'&marginId='+tier.id; |
149 | 149 | ||
150 | updateFuelManagerService.editTier(editTierData).then(function(result) { | 150 | updateFuelManagerService.editTier(editTierData).then(function(result) { |
151 | toastr.success('Successfully Updated', { | 151 | toastr.success('Successfully Updated', { |
152 | closeButton: true | 152 | closeButton: true |
153 | }) | 153 | }) |
154 | updateFuelManagerService.getJetTiers(tier.marginTemplate.id).then(function(tiers) { | 154 | updateFuelManagerService.getJetTiers(tier.marginTemplate.id).then(function(tiers) { |
155 | $scope.vTypeJets[index].tierList = tiers; | 155 | $scope.vTypeJets[index].tierList = tiers; |
156 | $scope.showLoader = false; | 156 | $scope.showLoader = false; |
157 | }) | 157 | }) |
158 | }) | 158 | }) |
159 | 159 | ||
160 | } | 160 | } |
161 | 161 | ||
162 | $scope.deleteTierObject = {}; | 162 | $scope.deleteTierObject = {}; |
163 | $scope.deleteTier = function(id, jetid, index){ | 163 | $scope.deleteTier = function(id, jetid, index){ |
164 | $scope.deleteTierObject.id = id; | 164 | $scope.deleteTierObject.id = id; |
165 | $scope.deleteTierObject.jetId = jetid; | 165 | $scope.deleteTierObject.jetId = jetid; |
166 | $scope.deleteTierObject.index = index; | 166 | $scope.deleteTierObject.index = index; |
167 | $('#deleteTierConfirm').css('display', 'block'); | 167 | $('#deleteTierConfirm').css('display', 'block'); |
168 | } | 168 | } |
169 | 169 | ||
170 | $scope.confirmDeleteTier = function(){ | 170 | $scope.confirmDeleteTier = function(){ |
171 | $scope.showLoader = true; | 171 | $scope.showLoader = true; |
172 | updateFuelManagerService.deleteTier($scope.deleteTierObject.id).then(function(result) { | 172 | updateFuelManagerService.deleteTier($scope.deleteTierObject.id).then(function(result) { |
173 | toastr.success(''+result.success+'', { | 173 | toastr.success(''+result.success+'', { |
174 | closeButton: true | 174 | closeButton: true |
175 | }) | 175 | }) |
176 | updateFuelManagerService.getJetTiers($scope.deleteTierObject.jetId).then(function(tiers) { | 176 | updateFuelManagerService.getJetTiers($scope.deleteTierObject.jetId).then(function(tiers) { |
177 | $scope.aTypeJets[$scope.deleteTierObject.index].tierList = tiers; | 177 | $scope.aTypeJets[$scope.deleteTierObject.index].tierList = tiers; |
178 | $scope.showLoader = false; | 178 | $scope.showLoader = false; |
179 | $scope.deleteTierObject = {}; | 179 | $scope.deleteTierObject = {}; |
180 | }) | 180 | }) |
181 | }) | 181 | }) |
182 | $('#deleteTierConfirm').css('display', 'none'); | 182 | $('#deleteTierConfirm').css('display', 'none'); |
183 | } | 183 | } |
184 | 184 | ||
185 | $scope.cancelTierDelete = function(){ | 185 | $scope.cancelTierDelete = function(){ |
186 | console.log('cancel'); | 186 | console.log('cancel'); |
187 | $('#deleteTierConfirm').css('display', 'none'); | 187 | $('#deleteTierConfirm').css('display', 'none'); |
188 | $scope.deleteTierObject = {}; | 188 | $scope.deleteTierObject = {}; |
189 | } | 189 | } |
190 | 190 | ||
191 | /*$scope.deleteVtypeTier = function(id, jetid, index){ | 191 | /*$scope.deleteVtypeTier = function(id, jetid, index){ |
192 | $scope.showLoader = true; | 192 | $scope.showLoader = true; |
193 | updateFuelManagerService.deleteTier(id).then(function(result) { | 193 | updateFuelManagerService.deleteTier(id).then(function(result) { |
194 | toastr.success(''+result.success+'', { | 194 | toastr.success(''+result.success+'', { |
195 | closeButton: true | 195 | closeButton: true |
196 | }) | 196 | }) |
197 | updateFuelManagerService.getJetTiers(jetid).then(function(tiers) { | 197 | updateFuelManagerService.getJetTiers(jetid).then(function(tiers) { |
198 | $scope.vTypeJets[index].tierList = tiers; | 198 | $scope.vTypeJets[index].tierList = tiers; |
199 | $scope.showLoader = false; | 199 | $scope.showLoader = false; |
200 | }) | 200 | }) |
201 | }) | 201 | }) |
202 | }*/ | 202 | }*/ |
203 | 203 | ||
204 | $scope.deleteVtypeTierObject = {}; | 204 | $scope.deleteVtypeTierObject = {}; |
205 | $scope.deleteVtypeTier = function(id, jetid, index){ | 205 | $scope.deleteVtypeTier = function(id, jetid, index){ |
206 | $scope.deleteVtypeTierObject.id = id; | 206 | $scope.deleteVtypeTierObject.id = id; |
207 | $scope.deleteVtypeTierObject.jetId = jetid; | 207 | $scope.deleteVtypeTierObject.jetId = jetid; |
208 | $scope.deleteVtypeTierObject.index = index; | 208 | $scope.deleteVtypeTierObject.index = index; |
209 | $('#deleteVtypeTierConfirm').css('display', 'block'); | 209 | $('#deleteVtypeTierConfirm').css('display', 'block'); |
210 | } | 210 | } |
211 | 211 | ||
212 | $scope.confirmDeleteVtypeTier = function(){ | 212 | $scope.confirmDeleteVtypeTier = function(){ |
213 | $scope.showLoader = true; | 213 | $scope.showLoader = true; |
214 | updateFuelManagerService.deleteTier($scope.deleteVtypeTierObject.id).then(function(result) { | 214 | updateFuelManagerService.deleteTier($scope.deleteVtypeTierObject.id).then(function(result) { |
215 | toastr.success(''+result.success+'', { | 215 | toastr.success(''+result.success+'', { |
216 | closeButton: true | 216 | closeButton: true |
217 | }) | 217 | }) |
218 | updateFuelManagerService.getJetTiers($scope.deleteVtypeTierObject.jetId).then(function(tiers) { | 218 | updateFuelManagerService.getJetTiers($scope.deleteVtypeTierObject.jetId).then(function(tiers) { |
219 | $scope.vTypeJets[$scope.deleteVtypeTierObject.index].tierList = tiers; | 219 | $scope.vTypeJets[$scope.deleteVtypeTierObject.index].tierList = tiers; |
220 | $scope.showLoader = false; | 220 | $scope.showLoader = false; |
221 | $scope.deleteVtypeTierObject = {}; | 221 | $scope.deleteVtypeTierObject = {}; |
222 | }) | 222 | }) |
223 | }) | 223 | }) |
224 | $('#deleteVtypeTierConfirm').css('display', 'none'); | 224 | $('#deleteVtypeTierConfirm').css('display', 'none'); |
225 | } | 225 | } |
226 | 226 | ||
227 | $scope.cancelVtypeTierDelete = function(){ | 227 | $scope.cancelVtypeTierDelete = function(){ |
228 | console.log('cancel'); | 228 | console.log('cancel'); |
229 | $('#deleteVtypeTierConfirm').css('display', 'none'); | 229 | $('#deleteVtypeTierConfirm').css('display', 'none'); |
230 | $scope.deleteVtypeTierObject = {}; | 230 | $scope.deleteVtypeTierObject = {}; |
231 | } | 231 | } |
232 | 232 | ||
233 | $scope.saveJetAccordian = function(jets){ | 233 | $scope.saveJetAccordian = function(jets){ |
234 | $scope.showLoader = true; | 234 | $scope.showLoader = true; |
235 | $scope.jetsDetail = jets; | 235 | $scope.jetsDetail = jets; |
236 | $scope.jetsDetail.userProfileId = $scope.userProfileId; | 236 | $scope.jetsDetail.userProfileId = $scope.userProfileId; |
237 | //console.log('jets', $scope.jetsDetail); | 237 | //console.log('jets', $scope.jetsDetail); |
238 | $('.'+$scope.jetsDetail.id).slideUp(); | 238 | $('.'+$scope.jetsDetail.id).slideUp(); |
239 | $('#'+$scope.jetsDetail.id).removeClass('customActive'); | 239 | $('#'+$scope.jetsDetail.id).removeClass('customActive'); |
240 | $('#'+$scope.jetsDetail.id+' select, #'+$scope.jetsDetail.id+' input').prop("disabled", true); | 240 | $('#'+$scope.jetsDetail.id+' select, #'+$scope.jetsDetail.id+' input').prop("disabled", true); |
241 | $('#'+$scope.jetsDetail.id+' .btn-success, #'+$scope.jetsDetail.id+' .btn-danger').css('display', 'none'); | 241 | $('#'+$scope.jetsDetail.id+' .btn-success, #'+$scope.jetsDetail.id+' .btn-danger').css('display', 'none'); |
242 | $('#'+$scope.jetsDetail.id+' .btn-default').css('display', 'inline-block'); | 242 | $('#'+$scope.jetsDetail.id+' .btn-default').css('display', 'inline-block'); |
243 | $('#'+$scope.jetsDetail.id+' .btn-primary').css('display', 'inline-block'); | 243 | $('#'+$scope.jetsDetail.id+' .btn-primary').css('display', 'inline-block'); |
244 | 244 | ||
245 | var editJetData = 'productType='+$scope.jetsDetail.productType+'&marginName='+$scope.jetsDetail.marginName+'&pricingStructure='+$scope.jetsDetail.pricingStructure+'&marginValue='+$scope.jetsDetail.marginValue+'&userProfileId='+$scope.jetsDetail.userProfileId+'&marginId='+$scope.jetsDetail.id+'&message='+$scope.jetsDetail.message; | 245 | var editJetData = 'productType='+$scope.jetsDetail.productType+'&marginName='+$scope.jetsDetail.marginName+'&pricingStructure='+$scope.jetsDetail.pricingStructure+'&marginValue='+$scope.jetsDetail.marginValue+'&userProfileId='+$scope.jetsDetail.userProfileId+'&marginId='+$scope.jetsDetail.id+'&message='+$scope.jetsDetail.message; |
246 | 246 | ||
247 | updateFuelManagerService.editAtypeJetMargin(editJetData).then(function(result) { | 247 | updateFuelManagerService.editAtypeJetMargin(editJetData).then(function(result) { |
248 | console.log('newJet', editJetData); | 248 | console.log('newJet', editJetData); |
249 | toastr.success('Successfully Updated', { | 249 | toastr.success('Successfully Updated', { |
250 | closeButton: true | 250 | closeButton: true |
251 | }) | 251 | }) |
252 | updateFuelManagerService.getATypeJets($scope.userProfileId).then(function(result) { | 252 | updateFuelManagerService.getATypeJets($scope.userProfileId).then(function(result) { |
253 | console.log('result', result); | 253 | console.log('result', result); |
254 | $scope.aTypeJets = result; | 254 | $scope.aTypeJets = result; |
255 | $scope.showLoader = false; | 255 | $scope.showLoader = false; |
256 | }) | 256 | }) |
257 | }) | 257 | }) |
258 | 258 | ||
259 | } | 259 | } |
260 | 260 | ||
261 | $scope.closeAccordian = function(jets){ | 261 | $scope.closeAccordian = function(jets){ |
262 | $('.'+jets.id).slideUp(); | 262 | $('.'+jets.id).slideUp(); |
263 | $('#'+jets.id).removeClass('customActive'); | 263 | $('#'+jets.id).removeClass('customActive'); |
264 | $('#'+jets.id+' select, #'+jets.id+' input').prop("disabled", true); | 264 | $('#'+jets.id+' select, #'+jets.id+' input').prop("disabled", true); |
265 | $('#'+jets.id+' .btn-success, #'+jets.id+' .btn-danger').css('display', 'none'); | 265 | $('#'+jets.id+' .btn-success, #'+jets.id+' .btn-danger').css('display', 'none'); |
266 | $('#'+jets.id+' .btn-default').css('display', 'inline-block'); | 266 | $('#'+jets.id+' .btn-default').css('display', 'inline-block'); |
267 | $('#'+jets.id+' .btn-primary').css('display', 'inline-block'); | 267 | $('#'+jets.id+' .btn-primary').css('display', 'inline-block'); |
268 | } | 268 | } |
269 | 269 | ||
270 | $scope.closeAccordianVtype = function(jets){ | 270 | $scope.closeAccordianVtype = function(jets){ |
271 | $('.'+jets.id).slideUp(); | 271 | $('.'+jets.id).slideUp(); |
272 | $('#'+jets.id).removeClass('customActive'); | 272 | $('#'+jets.id).removeClass('customActive'); |
273 | $('#'+jets.id+' select, #'+jets.id+' input').prop("disabled", true); | 273 | $('#'+jets.id+' select, #'+jets.id+' input').prop("disabled", true); |
274 | $('#'+jets.id+' .btn-success, #'+jets.id+' .btn-danger').css('display', 'none'); | 274 | $('#'+jets.id+' .btn-success, #'+jets.id+' .btn-danger').css('display', 'none'); |
275 | $('#'+jets.id+' .btn-default').css('display', 'inline-block'); | 275 | $('#'+jets.id+' .btn-default').css('display', 'inline-block'); |
276 | $('#'+jets.id+' .btn-primary').css('display', 'inline-block'); | 276 | $('#'+jets.id+' .btn-primary').css('display', 'inline-block'); |
277 | } | 277 | } |
278 | 278 | ||
279 | $scope.saveVtypeJetAccordian = function(jets){ | 279 | $scope.saveVtypeJetAccordian = function(jets){ |
280 | $scope.showLoader = true; | 280 | $scope.showLoader = true; |
281 | $scope.jetsDetail = jets; | 281 | $scope.jetsDetail = jets; |
282 | $scope.jetsDetail.userProfileId = $scope.userProfileId; | 282 | $scope.jetsDetail.userProfileId = $scope.userProfileId; |
283 | //console.log('jets', $scope.jetsDetail); | 283 | //console.log('jets', $scope.jetsDetail); |
284 | $('.'+$scope.jetsDetail.id).slideUp(); | 284 | $('.'+$scope.jetsDetail.id).slideUp(); |
285 | $('#'+$scope.jetsDetail.id).removeClass('customActive'); | 285 | $('#'+$scope.jetsDetail.id).removeClass('customActive'); |
286 | $('#'+$scope.jetsDetail.id+' select, #'+$scope.jetsDetail.id+' input').prop("disabled", true); | 286 | $('#'+$scope.jetsDetail.id+' select, #'+$scope.jetsDetail.id+' input').prop("disabled", true); |
287 | $('#'+$scope.jetsDetail.id+' .btn-success, #'+$scope.jetsDetail.id+' .btn-danger').css('display', 'none'); | 287 | $('#'+$scope.jetsDetail.id+' .btn-success, #'+$scope.jetsDetail.id+' .btn-danger').css('display', 'none'); |
288 | $('#'+$scope.jetsDetail.id+' .btn-default').css('display', 'inline-block'); | 288 | $('#'+$scope.jetsDetail.id+' .btn-default').css('display', 'inline-block'); |
289 | $('#'+jets.id+' .btn-primary').css('display', 'inline-block'); | 289 | $('#'+jets.id+' .btn-primary').css('display', 'inline-block'); |
290 | 290 | ||
291 | var editVtypeJetData = 'productType='+$scope.jetsDetail.productType+'&marginName='+$scope.jetsDetail.marginName+'&pricingStructure='+$scope.jetsDetail.pricingStructure+'&marginValue='+$scope.jetsDetail.marginValue+'&userProfileId='+$scope.jetsDetail.userProfileId+'&marginId='+$scope.jetsDetail.id+'&message='+$scope.jetsDetail.message; | 291 | var editVtypeJetData = 'productType='+$scope.jetsDetail.productType+'&marginName='+$scope.jetsDetail.marginName+'&pricingStructure='+$scope.jetsDetail.pricingStructure+'&marginValue='+$scope.jetsDetail.marginValue+'&userProfileId='+$scope.jetsDetail.userProfileId+'&marginId='+$scope.jetsDetail.id+'&message='+$scope.jetsDetail.message; |
292 | 292 | ||
293 | updateFuelManagerService.editVtypeJetMargin(editVtypeJetData).then(function(result) { | 293 | updateFuelManagerService.editVtypeJetMargin(editVtypeJetData).then(function(result) { |
294 | console.log('newJet', editVtypeJetData); | 294 | console.log('newJet', editVtypeJetData); |
295 | toastr.success('Successfully Updated', { | 295 | toastr.success('Successfully Updated', { |
296 | closeButton: true | 296 | closeButton: true |
297 | }) | 297 | }) |
298 | updateFuelManagerService.getVTypeJets($scope.userProfileId).then(function(result) { | 298 | updateFuelManagerService.getVTypeJets($scope.userProfileId).then(function(result) { |
299 | $scope.vTypeJets = result; | 299 | $scope.vTypeJets = result; |
300 | console.log('second jets', result); | 300 | console.log('second jets', result); |
301 | $scope.showLoader = false; | 301 | $scope.showLoader = false; |
302 | }) | 302 | }) |
303 | }) | 303 | }) |
304 | 304 | ||
305 | } | 305 | } |
306 | 306 | ||
307 | $scope.newJet = {}; | 307 | $scope.newJet = {}; |
308 | 308 | ||
309 | $scope.addNewMarginBtn = function(){ | 309 | $scope.addNewMarginBtn = function(){ |
310 | $('.addNewMargin').css('display', 'block'); | 310 | $('.addNewMargin').css('display', 'block'); |
311 | } | 311 | } |
312 | $scope.closeMarginPopup = function(){ | 312 | $scope.closeMarginPopup = function(){ |
313 | $('.addNewMargin').css('display', 'none'); | 313 | $('.addNewMargin').css('display', 'none'); |
314 | $scope.newJet = {}; | 314 | $scope.newJet = {}; |
315 | } | 315 | } |
316 | 316 | ||
317 | //$scope.newJet.productType = ''; | 317 | //$scope.newJet.productType = ''; |
318 | 318 | ||
319 | $scope.addNewATypeJet = function(){ | 319 | $scope.addNewATypeJet = function(){ |
320 | $scope.showLoader = true; | 320 | $scope.showLoader = true; |
321 | $scope.newJet.productType = 'JET-A'; | 321 | $scope.newJet.productType = 'JET-A'; |
322 | $scope.newJet.userProfileId = $scope.userProfileId; | 322 | $scope.newJet.userProfileId = $scope.userProfileId; |
323 | 323 | ||
324 | var jetData = 'productType='+$scope.newJet.productType+'&marginName='+$scope.newJet.marginName+'&pricingStructure='+$scope.newJet.pricingStructure+'&marginValue='+$scope.newJet.marginValue+'&userProfileId='+$scope.newJet.userProfileId+'&message='+$scope.newJet.message; | 324 | var jetData = 'productType='+$scope.newJet.productType+'&marginName='+$scope.newJet.marginName+'&pricingStructure='+$scope.newJet.pricingStructure+'&marginValue='+$scope.newJet.marginValue+'&userProfileId='+$scope.newJet.userProfileId+'&message='+$scope.newJet.message; |
325 | 325 | ||
326 | updateFuelManagerService.addNewAtypeJetMargin(jetData).then(function(result) { | 326 | updateFuelManagerService.addNewAtypeJetMargin(jetData).then(function(result) { |
327 | console.log('newJet', jetData); | 327 | console.log('newJet', jetData); |
328 | toastr.success('Successfully Added', { | 328 | toastr.success('Successfully Added', { |
329 | closeButton: true | 329 | closeButton: true |
330 | }) | 330 | }) |
331 | $('.addNewMargin').css('display', 'none'); | 331 | $('.addNewMargin').css('display', 'none'); |
332 | updateFuelManagerService.getATypeJets($scope.userProfileId).then(function(result) { | 332 | updateFuelManagerService.getATypeJets($scope.userProfileId).then(function(result) { |
333 | console.log('result', result); | 333 | console.log('result', result); |
334 | $scope.aTypeJets = result; | 334 | $scope.aTypeJets = result; |
335 | $scope.showLoader = false; | 335 | $scope.showLoader = false; |
336 | }) | 336 | }) |
337 | }) | 337 | }) |
338 | } | 338 | } |
339 | 339 | ||
340 | $scope.newVtypeJet = {}; | 340 | $scope.newVtypeJet = {}; |
341 | 341 | ||
342 | $scope.addNewVtypePop = function(){ | 342 | $scope.addNewVtypePop = function(){ |
343 | $('.addNewVtype').css('display', 'block'); | 343 | $('.addNewVtype').css('display', 'block'); |
344 | } | 344 | } |
345 | $scope.closeNewVtypePop = function(){ | 345 | $scope.closeNewVtypePop = function(){ |
346 | $('.addNewVtype').css('display', 'none'); | 346 | $('.addNewVtype').css('display', 'none'); |
347 | $scope.newVtypeJet = {}; | 347 | $scope.newVtypeJet = {}; |
348 | } | 348 | } |
349 | 349 | ||
350 | $scope.addNewVTypeJet = function(){ | 350 | $scope.addNewVTypeJet = function(){ |
351 | $scope.showLoader = true; | 351 | $scope.showLoader = true; |
352 | $scope.newVtypeJet.productType = 'AVGAS'; | 352 | $scope.newVtypeJet.productType = 'AVGAS'; |
353 | $scope.newVtypeJet.userProfileId = $scope.userProfileId; | 353 | $scope.newVtypeJet.userProfileId = $scope.userProfileId; |
354 | 354 | ||
355 | var vJetData = 'productType='+$scope.newVtypeJet.productType+'&marginName='+$scope.newVtypeJet.marginName+'&pricingStructure='+$scope.newVtypeJet.pricingStructure+'&marginValue='+$scope.newVtypeJet.marginValue+'&userProfileId='+$scope.newVtypeJet.userProfileId+'&message='+$scope.newVtypeJet.message; | 355 | var vJetData = 'productType='+$scope.newVtypeJet.productType+'&marginName='+$scope.newVtypeJet.marginName+'&pricingStructure='+$scope.newVtypeJet.pricingStructure+'&marginValue='+$scope.newVtypeJet.marginValue+'&userProfileId='+$scope.newVtypeJet.userProfileId+'&message='+$scope.newVtypeJet.message; |
356 | 356 | ||
357 | updateFuelManagerService.addNewVtypeJet(vJetData).then(function(result) { | 357 | updateFuelManagerService.addNewVtypeJet(vJetData).then(function(result) { |
358 | 358 | ||
359 | toastr.success('Successfully Added', { | 359 | toastr.success('Successfully Added', { |
360 | closeButton: true | 360 | closeButton: true |
361 | }) | 361 | }) |
362 | $('.addNewVtype').css('display', 'none'); | 362 | $('.addNewVtype').css('display', 'none'); |
363 | updateFuelManagerService.getVTypeJets($scope.userProfileId).then(function(result) { | 363 | updateFuelManagerService.getVTypeJets($scope.userProfileId).then(function(result) { |
364 | $scope.vTypeJets = result; | 364 | $scope.vTypeJets = result; |
365 | $scope.showLoader = false; | 365 | $scope.showLoader = false; |
366 | }) | 366 | }) |
367 | }) | 367 | }) |
368 | 368 | ||
369 | } | 369 | } |
370 | 370 | ||
371 | $scope.emailForMargin; | 371 | $scope.emailForMargin; |
372 | $scope.emailPricingForMargin = function(value){ | 372 | $scope.emailPricingForMargin = function(value){ |
373 | $('#confirm2').css('display', 'block'); | 373 | $('#confirm2').css('display', 'block'); |
374 | $scope.emailForMargin = value; | 374 | $scope.emailForMargin = value; |
375 | 375 | ||
376 | } | 376 | } |
377 | $scope.saveAndCloseForMarginConfirm = function(){ | 377 | $scope.saveAndCloseForMarginConfirm = function(){ |
378 | $('#confirm2').css('display', 'none'); | 378 | $('#confirm2').css('display', 'none'); |
379 | updateFuelManagerService.sendMailToMargin($scope.emailForMargin).then(function(result) { | 379 | updateFuelManagerService.sendMailToMargin($scope.emailForMargin).then(function(result) { |
380 | toastr.success(''+result.success+'', { | 380 | toastr.success(''+result.success+'', { |
381 | closeButton: true | 381 | closeButton: true |
382 | }) | 382 | }) |
383 | }) | 383 | }) |
384 | } | 384 | } |
385 | $scope.cancelAndCloseForMarginConfirm = function(){ | 385 | $scope.cancelAndCloseForMarginConfirm = function(){ |
386 | $('#confirm2').css('display', 'none'); | 386 | $('#confirm2').css('display', 'none'); |
387 | } | 387 | } |
388 | 388 | ||
389 | $scope.sendEmail = {}; | 389 | $scope.sendEmail = {}; |
390 | 390 | ||
391 | $scope.confirmMail = function(){ | 391 | $scope.confirmMail = function(){ |
392 | if ($scope.sendEmail.pricing != '' && $scope.sendEmail.pricing != null && $scope.sendEmail.pricing != undefined) { | 392 | if ($scope.sendEmail.pricing != '' && $scope.sendEmail.pricing != null && $scope.sendEmail.pricing != undefined) { |
393 | $('#confirm1').css('display', 'block'); | 393 | $('#confirm1').css('display', 'block'); |
394 | } | 394 | } |
395 | } | 395 | } |
396 | 396 | ||
397 | $scope.saveAndCloseConfirm = function(){ | 397 | $scope.saveAndCloseConfirm = function(){ |
398 | $('#confirm1').css('display', 'none'); | 398 | $('#confirm1').css('display', 'none'); |
399 | updateFuelManagerService.sendMailToGroupMargin($scope.sendEmail.pricing).then(function(result) { | 399 | updateFuelManagerService.sendMailToGroupMargin($scope.sendEmail.pricing).then(function(result) { |
400 | toastr.success(''+result.success+'', { | 400 | toastr.success(''+result.success+'', { |
401 | closeButton: true | 401 | closeButton: true |
402 | }) | 402 | }) |
403 | }) | 403 | }) |
404 | } | 404 | } |
405 | $scope.cancelAndCloseConfirm = function(){ | 405 | $scope.cancelAndCloseConfirm = function(){ |
406 | $scope.sendEmail = {}; | 406 | $scope.sendEmail = {}; |
407 | $scope.sendEmail.pricing = ''; | 407 | $scope.sendEmail.pricing = ''; |
408 | $('#confirm1').css('display', 'none'); | 408 | $('#confirm1').css('display', 'none'); |
409 | } | 409 | } |
410 | 410 | ||
411 | $scope.newFuelPricing = {}; | 411 | $scope.newFuelPricing = {}; |
412 | updateFuelManagerService.getFuelPricingNew().then(function(result) { | 412 | updateFuelManagerService.getFuelPricingNew().then(function(result) { |
413 | $scope.newFuelPricing = result; | 413 | $scope.newFuelPricing = result; |
414 | for (var i = 0; i<$scope.newFuelPricing.length; i++) { | 414 | for (var i = 0; i<$scope.newFuelPricing.length; i++) { |
415 | if ($scope.newFuelPricing[i].fuelPricing != null) { | 415 | if ($scope.newFuelPricing[i].fuelPricing != null) { |
416 | if ($scope.newFuelPricing[i].fuelPricing.expirationDate != null && $scope.newFuelPricing[i].fuelPricing.expirationDate != '') { | 416 | if ($scope.newFuelPricing[i].fuelPricing.expirationDate != null && $scope.newFuelPricing[i].fuelPricing.expirationDate != '') { |
417 | var newTime = new Date($scope.newFuelPricing[i].fuelPricing.expirationDate); | 417 | var newTime = new Date($scope.newFuelPricing[i].fuelPricing.expirationDate); |
418 | var month = newTime.getUTCMonth() + 1; //months from 1-12 | 418 | var month = newTime.getUTCMonth() + 1; //months from 1-12 |
419 | var day = newTime.getUTCDate(); | 419 | var day = newTime.getUTCDate(); |
420 | var year = newTime.getUTCFullYear(); | 420 | var year = newTime.getUTCFullYear(); |
421 | $scope.newFuelPricing[i].fuelPricing.expirationDate = month+'/'+day+'/'+year; | 421 | $scope.newFuelPricing[i].fuelPricing.expirationDate = month+'/'+day+'/'+year; |
422 | } | 422 | } |
423 | } | 423 | } |
424 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { | 424 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { |
425 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { | 425 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { |
426 | if ($scope.newFuelPricing[i].futureFuelPricing.nextExpiration != null && $scope.newFuelPricing[i].futureFuelPricing.nextExpiration != '') { | 426 | if ($scope.newFuelPricing[i].futureFuelPricing.nextExpiration != null && $scope.newFuelPricing[i].futureFuelPricing.nextExpiration != '') { |
427 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.nextExpiration); | 427 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.nextExpiration); |
428 | var nextMonth = newTime.getUTCMonth() + 1; //months from 1-12 | 428 | var nextMonth = newTime.getUTCMonth() + 1; //months from 1-12 |
429 | var nextDay = newTime.getUTCDate(); | 429 | var nextDay = newTime.getUTCDate(); |
430 | var nextYear = newTime.getUTCFullYear(); | 430 | var nextYear = newTime.getUTCFullYear(); |
431 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = nextMonth+'/'+nextDay+'/'+nextYear; | 431 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = nextMonth+'/'+nextDay+'/'+nextYear; |
432 | } | 432 | } |
433 | } | 433 | } |
434 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { | 434 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { |
435 | if ($scope.newFuelPricing[i].futureFuelPricing.deployDate != null && $scope.newFuelPricing[i].futureFuelPricing.deployDate != '') { | 435 | if ($scope.newFuelPricing[i].futureFuelPricing.deployDate != null && $scope.newFuelPricing[i].futureFuelPricing.deployDate != '') { |
436 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.deployDate); | 436 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.deployDate); |
437 | var dmonth = newTime.getUTCMonth() + 1; //months from 1-12 | 437 | var dmonth = newTime.getUTCMonth() + 1; //months from 1-12 |
438 | var dday = newTime.getUTCDate(); | 438 | var dday = newTime.getUTCDate(); |
439 | var dyear = newTime.getUTCFullYear(); | 439 | var dyear = newTime.getUTCFullYear(); |
440 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = dmonth+'/'+dday+'/'+dyear; | 440 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = dmonth+'/'+dday+'/'+dyear; |
441 | } | 441 | } |
442 | } | 442 | } |
443 | } | 443 | } |
444 | 444 | ||
445 | var str =""+ $scope.newFuelPricing[i].name | 445 | var str =""+ $scope.newFuelPricing[i].name |
446 | if(str.startsWith("J")){ | 446 | if(str.startsWith("J")){ |
447 | $scope.newFuelPricing[i].jeta = true; | 447 | $scope.newFuelPricing[i].jeta = true; |
448 | var str1 = str.substring(0,5) | 448 | var str1 = str.substring(0,5) |
449 | var str2 = str.substring(6, str.length) | 449 | var str2 = str.substring(6, str.length) |
450 | $scope.newFuelPricing[i].name = str1 | 450 | $scope.newFuelPricing[i].name = str1 |
451 | $scope.newFuelPricing[i].namejetrest = str2 | 451 | $scope.newFuelPricing[i].namejetrest = str2 |
452 | 452 | ||
453 | 453 | ||
454 | }else if(str.startsWith("100")){ | 454 | }else if(str.startsWith("100")){ |
455 | $scope.newFuelPricing[i].avgas = true; | 455 | $scope.newFuelPricing[i].avgas = true; |
456 | var str1 = str.substring(0,5) | 456 | var str1 = str.substring(0,5) |
457 | var str2 = str.substring(6, str.length) | 457 | var str2 = str.substring(6, str.length) |
458 | $scope.newFuelPricing[i].name = str1 | 458 | $scope.newFuelPricing[i].name = str1 |
459 | $scope.newFuelPricing[i].nameavgasrest = str2 | 459 | $scope.newFuelPricing[i].nameavgasrest = str2 |
460 | } | 460 | } |
461 | } | 461 | } |
462 | $scope.showLoader = false; | 462 | $scope.showLoader = false; |
463 | 463 | ||
464 | }) | 464 | }) |
465 | $scope.$watch("fuelPricing.fuelPricing.expirationDate",function(old,newv){ | 465 | $scope.$watch("fuelPricing.fuelPricing.expirationDate",function(old,newv){ |
466 | }); | 466 | }); |
467 | $scope.updateFuelPricing = {}; | 467 | $scope.updateFuelPricing = {}; |
468 | $scope.updateFuelPricing.fuelPricingList = []; | 468 | $scope.updateFuelPricing.fuelPricingList = []; |
469 | $scope.updateFuelPricing.userProfileId = $scope.userProfileId; | 469 | $scope.updateFuelPricing.userProfileId = $scope.userProfileId; |
470 | $scope.updateFuelPricingClick = function(){ | 470 | $scope.updateFuelPricingClick = function(){ |
471 | $scope.showLoader = true; | 471 | $scope.showLoader = true; |
472 | 472 | ||
473 | for (var i = 0; i<$scope.newFuelPricing.length; i++) { | 473 | for (var i = 0; i<$scope.newFuelPricing.length; i++) { |
474 | if ($scope.newFuelPricing[i].fuelPricing != null) { | 474 | if ($scope.newFuelPricing[i].fuelPricing != null) { |
475 | $scope.newFuelPricing[i].fuelPricing.papTotal = parseFloat($scope.newFuelPricing[i].fuelPricing.cost) + parseFloat($scope.newFuelPricing[i].fuelPricing.papMargin); | 475 | $scope.newFuelPricing[i].fuelPricing.papTotal = parseFloat($scope.newFuelPricing[i].fuelPricing.cost) + parseFloat($scope.newFuelPricing[i].fuelPricing.papMargin); |
476 | if ($scope.newFuelPricing[i].fuelPricing.cost == null) { | 476 | if ($scope.newFuelPricing[i].fuelPricing.cost == null) { |
477 | $scope.newFuelPricing[i].fuelPricing.cost = ''; | 477 | $scope.newFuelPricing[i].fuelPricing.cost = ''; |
478 | } | 478 | } |
479 | if ($scope.newFuelPricing[i].fuelPricing.papMargin == null) { | 479 | if ($scope.newFuelPricing[i].fuelPricing.papMargin == null) { |
480 | $scope.newFuelPricing[i].fuelPricing.papMargin = ''; | 480 | $scope.newFuelPricing[i].fuelPricing.papMargin = ''; |
481 | } | 481 | } |
482 | if ($scope.newFuelPricing[i].fuelPricing.papTotal == null) { | 482 | if ($scope.newFuelPricing[i].fuelPricing.papTotal == null) { |
483 | $scope.newFuelPricing[i].fuelPricing.papTotal = ''; | 483 | $scope.newFuelPricing[i].fuelPricing.papTotal = ''; |
484 | } | 484 | } |
485 | if ($scope.newFuelPricing[i].fuelPricing.expirationDate == null) { | 485 | if ($scope.newFuelPricing[i].fuelPricing.expirationDate == null) { |
486 | $scope.newFuelPricing[i].fuelPricing.expirationDate = ''; | 486 | $scope.newFuelPricing[i].fuelPricing.expirationDate = ''; |
487 | }else{ | 487 | }else{ |
488 | $scope.newFuelPricing[i].fuelPricing.expirationDate = new Date($scope.newFuelPricing[i].fuelPricing.expirationDate); | 488 | $scope.newFuelPricing[i].fuelPricing.expirationDate = new Date($scope.newFuelPricing[i].fuelPricing.expirationDate); |
489 | console.log('$scope.newFuelPricing[i].fuelPricing.expirationDate', $scope.newFuelPricing[i].fuelPricing.expirationDate); | 489 | console.log('$scope.newFuelPricing[i].fuelPricing.expirationDate', $scope.newFuelPricing[i].fuelPricing.expirationDate); |
490 | $scope.newFuelPricing[i].fuelPricing.expirationDate = $scope.newFuelPricing[i].fuelPricing.expirationDate.getTime(); | 490 | $scope.newFuelPricing[i].fuelPricing.expirationDate = $scope.newFuelPricing[i].fuelPricing.expirationDate.getTime(); |
491 | } | 491 | } |
492 | 492 | ||
493 | $scope.newFuelPricing[i].fuelPricing.papTotal = parseFloat($scope.newFuelPricing[i].fuelPricing.cost) + parseFloat($scope.newFuelPricing[i].fuelPricing.papMargin); | 493 | $scope.newFuelPricing[i].fuelPricing.papTotal = parseFloat($scope.newFuelPricing[i].fuelPricing.cost) + parseFloat($scope.newFuelPricing[i].fuelPricing.papMargin); |
494 | $scope.updateFuelPricing.fuelPricingList.push({ | 494 | $scope.updateFuelPricing.fuelPricingList.push({ |
495 | 'cost': $scope.newFuelPricing[i].fuelPricing.cost, | 495 | 'cost': $scope.newFuelPricing[i].fuelPricing.cost, |
496 | 'papMargin': $scope.newFuelPricing[i].fuelPricing.papMargin, | 496 | 'papMargin': $scope.newFuelPricing[i].fuelPricing.papMargin, |
497 | 'papTotal': $scope.newFuelPricing[i].fuelPricing.papTotal, | 497 | 'papTotal': $scope.newFuelPricing[i].fuelPricing.papTotal, |
498 | 'expirationDate': $scope.newFuelPricing[i].fuelPricing.expirationDate, | 498 | 'expirationDate': $scope.newFuelPricing[i].fuelPricing.expirationDate, |
499 | 'productId': $scope.newFuelPricing[i].id, | 499 | 'productId': $scope.newFuelPricing[i].id, |
500 | 'id': $scope.newFuelPricing[i].fuelPricing.id, | 500 | 'id': $scope.newFuelPricing[i].fuelPricing.id, |
501 | }) | 501 | }) |
502 | 502 | ||
503 | }else{ | 503 | }else{ |
504 | /*$scope.newFuelPricing[i].fuelPricing.cost = ''; | 504 | /*$scope.newFuelPricing[i].fuelPricing.cost = ''; |
505 | $scope.newFuelPricing[i].fuelPricing.papMargin = ''; | 505 | $scope.newFuelPricing[i].fuelPricing.papMargin = ''; |
506 | $scope.newFuelPricing[i].fuelPricing.papTotal = ''; | 506 | $scope.newFuelPricing[i].fuelPricing.papTotal = ''; |
507 | $scope.newFuelPricing[i].fuelPricing.expirationDate = '';*/ | 507 | $scope.newFuelPricing[i].fuelPricing.expirationDate = '';*/ |
508 | } | 508 | } |
509 | 509 | ||
510 | } | 510 | } |
511 | updateFuelManagerService.updateFuelPricing($scope.updateFuelPricing).then(function(result) { | 511 | updateFuelManagerService.updateFuelPricing($scope.updateFuelPricing).then(function(result) { |
512 | toastr.success('Successfully Updated', { | 512 | toastr.success('Successfully Updated', { |
513 | closeButton: true | 513 | closeButton: true |
514 | }) | 514 | }) |
515 | updateFuelManagerService.getFuelPricingNew().then(function(result) { | 515 | updateFuelManagerService.getFuelPricingNew().then(function(result) { |
516 | $scope.newFuelPricing = result; | 516 | $scope.newFuelPricing = result; |
517 | for (var i = 0; i<$scope.newFuelPricing.length; i++) { | 517 | for (var i = 0; i<$scope.newFuelPricing.length; i++) { |
518 | if ($scope.newFuelPricing[i].fuelPricing != null) { | 518 | if ($scope.newFuelPricing[i].fuelPricing != null) { |
519 | if ($scope.newFuelPricing[i].fuelPricing.expirationDate != null && $scope.newFuelPricing[i].fuelPricing.expirationDate != '') { | 519 | if ($scope.newFuelPricing[i].fuelPricing.expirationDate != null && $scope.newFuelPricing[i].fuelPricing.expirationDate != '') { |
520 | var newTime = new Date($scope.newFuelPricing[i].fuelPricing.expirationDate); | 520 | var newTime = new Date($scope.newFuelPricing[i].fuelPricing.expirationDate); |
521 | var month = newTime.getUTCMonth() + 1; //months from 1-12 | 521 | var month = newTime.getUTCMonth() + 1; //months from 1-12 |
522 | var day = newTime.getUTCDate(); | 522 | var day = newTime.getUTCDate(); |
523 | var year = newTime.getUTCFullYear(); | 523 | var year = newTime.getUTCFullYear(); |
524 | $scope.newFuelPricing[i].fuelPricing.expirationDate = month+'/'+day+'/'+year; | 524 | $scope.newFuelPricing[i].fuelPricing.expirationDate = month+'/'+day+'/'+year; |
525 | } | 525 | } |
526 | } | 526 | } |
527 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { | 527 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { |
528 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { | 528 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { |
529 | if ($scope.newFuelPricing[i].futureFuelPricing.nextExpiration != null && $scope.newFuelPricing[i].futureFuelPricing.nextExpiration != '') { | 529 | if ($scope.newFuelPricing[i].futureFuelPricing.nextExpiration != null && $scope.newFuelPricing[i].futureFuelPricing.nextExpiration != '') { |
530 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.nextExpiration); | 530 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.nextExpiration); |
531 | var nextMonth = newTime.getUTCMonth() + 1; //months from 1-12 | 531 | var nextMonth = newTime.getUTCMonth() + 1; //months from 1-12 |
532 | var nextDay = newTime.getUTCDate(); | 532 | var nextDay = newTime.getUTCDate(); |
533 | var nextYear = newTime.getUTCFullYear(); | 533 | var nextYear = newTime.getUTCFullYear(); |
534 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = nextMonth+'/'+nextDay+'/'+nextYear; | 534 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = nextMonth+'/'+nextDay+'/'+nextYear; |
535 | } | 535 | } |
536 | } | 536 | } |
537 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { | 537 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { |
538 | if ($scope.newFuelPricing[i].futureFuelPricing.deployDate != null && $scope.newFuelPricing[i].futureFuelPricing.deployDate != '') { | 538 | if ($scope.newFuelPricing[i].futureFuelPricing.deployDate != null && $scope.newFuelPricing[i].futureFuelPricing.deployDate != '') { |
539 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.deployDate); | 539 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.deployDate); |
540 | var dmonth = newTime.getUTCMonth() + 1; //months from 1-12 | 540 | var dmonth = newTime.getUTCMonth() + 1; //months from 1-12 |
541 | var dday = newTime.getUTCDate(); | 541 | var dday = newTime.getUTCDate(); |
542 | var dyear = newTime.getUTCFullYear(); | 542 | var dyear = newTime.getUTCFullYear(); |
543 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = dmonth+'/'+dday+'/'+dyear; | 543 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = dmonth+'/'+dday+'/'+dyear; |
544 | } | 544 | } |
545 | } | 545 | } |
546 | } | 546 | } |
547 | } | 547 | } |
548 | $scope.showLoader = false; | 548 | $scope.showLoader = false; |
549 | }) | 549 | }) |
550 | }) | 550 | }) |
551 | 551 | ||
552 | } | 552 | } |
553 | 553 | ||
554 | $scope.updateFutureFuelPricing = {}; | 554 | $scope.updateFutureFuelPricing = {}; |
555 | $scope.updateFutureFuelPricing.futureFuelPricingList = []; | 555 | $scope.updateFutureFuelPricing.futureFuelPricingList = []; |
556 | $scope.updateFutureFuelPricing.userProfileId = $scope.userProfileId; | 556 | $scope.updateFutureFuelPricing.userProfileId = $scope.userProfileId; |
557 | $scope.updateFutureFuelPricingClick = function(){ | 557 | $scope.updateFutureFuelPricingClick = function(){ |
558 | $scope.showLoader = true; | 558 | $scope.showLoader = true; |
559 | for (var i = 0; i<$scope.newFuelPricing.length; i++) { | 559 | for (var i = 0; i<$scope.newFuelPricing.length; i++) { |
560 | //console.log(parseFloat($scope.newFuelPricing[i].futureFuelPricing.cost) + parseFloat($scope.newFuelPricing[i].fuelPricing.papMargin)); | 560 | //console.log(parseFloat($scope.newFuelPricing[i].futureFuelPricing.cost) + parseFloat($scope.newFuelPricing[i].fuelPricing.papMargin)); |
561 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { | 561 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { |
562 | if ($scope.newFuelPricing[i].futureFuelPricing.cost != null || $scope.newFuelPricing[i].futureFuelPricing.cost != '' || $scope.newFuelPricing[i].futureFuelPricing.cost != undefined) { | 562 | if ($scope.newFuelPricing[i].futureFuelPricing.cost != null || $scope.newFuelPricing[i].futureFuelPricing.cost != '' || $scope.newFuelPricing[i].futureFuelPricing.cost != undefined) { |
563 | $scope.newFuelPricing[i].futureFuelPricing.papTotal = parseFloat($scope.newFuelPricing[i].futureFuelPricing.cost) + parseFloat($scope.newFuelPricing[i].fuelPricing.papMargin); | 563 | $scope.newFuelPricing[i].futureFuelPricing.papTotal = parseFloat($scope.newFuelPricing[i].futureFuelPricing.cost) + parseFloat($scope.newFuelPricing[i].fuelPricing.papMargin); |
564 | if ($scope.newFuelPricing[i].futureFuelPricing.cost == null) { | 564 | if ($scope.newFuelPricing[i].futureFuelPricing.cost == null) { |
565 | $scope.newFuelPricing[i].futureFuelPricing.cost = ''; | 565 | $scope.newFuelPricing[i].futureFuelPricing.cost = ''; |
566 | } | 566 | } |
567 | if ($scope.newFuelPricing[i].futureFuelPricing.papMargin == null) { | 567 | if ($scope.newFuelPricing[i].futureFuelPricing.papMargin == null) { |
568 | $scope.newFuelPricing[i].futureFuelPricing.papMargin = ''; | 568 | $scope.newFuelPricing[i].futureFuelPricing.papMargin = ''; |
569 | } | 569 | } |
570 | if ($scope.newFuelPricing[i].futureFuelPricing.papTotal == null) { | 570 | if ($scope.newFuelPricing[i].futureFuelPricing.papTotal == null) { |
571 | $scope.newFuelPricing[i].futureFuelPricing.papTotal = ''; | 571 | $scope.newFuelPricing[i].futureFuelPricing.papTotal = ''; |
572 | } | 572 | } |
573 | if ($scope.newFuelPricing[i].futureFuelPricing.nextExpiration == null) { | 573 | if ($scope.newFuelPricing[i].futureFuelPricing.nextExpiration == null) { |
574 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = ''; | 574 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = ''; |
575 | }else{ | 575 | }else{ |
576 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = new Date($scope.newFuelPricing[i].futureFuelPricing.nextExpiration); | 576 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = new Date($scope.newFuelPricing[i].futureFuelPricing.nextExpiration); |
577 | console.log('$scope.newFuelPricing[i].futureFuelPricing.nextExpiration', $scope.newFuelPricing[i].futureFuelPricing.nextExpiration); | 577 | console.log('$scope.newFuelPricing[i].futureFuelPricing.nextExpiration', $scope.newFuelPricing[i].futureFuelPricing.nextExpiration); |
578 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = $scope.newFuelPricing[i].futureFuelPricing.nextExpiration.getTime(); | 578 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = $scope.newFuelPricing[i].futureFuelPricing.nextExpiration.getTime(); |
579 | } | 579 | } |
580 | if ($scope.newFuelPricing[i].futureFuelPricing.deployDate == null) { | 580 | if ($scope.newFuelPricing[i].futureFuelPricing.deployDate == null) { |
581 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = ''; | 581 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = ''; |
582 | }else{ | 582 | }else{ |
583 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = new Date($scope.newFuelPricing[i].futureFuelPricing.deployDate); | 583 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = new Date($scope.newFuelPricing[i].futureFuelPricing.deployDate); |
584 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = $scope.newFuelPricing[i].futureFuelPricing.deployDate.getTime(); | 584 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = $scope.newFuelPricing[i].futureFuelPricing.deployDate.getTime(); |
585 | } | 585 | } |
586 | 586 | ||
587 | $scope.newFuelPricing[i].futureFuelPricing.papTotal = parseFloat($scope.newFuelPricing[i].futureFuelPricing.cost) + parseFloat($scope.newFuelPricing[i].fuelPricing.papMargin); | 587 | $scope.newFuelPricing[i].futureFuelPricing.papTotal = parseFloat($scope.newFuelPricing[i].futureFuelPricing.cost) + parseFloat($scope.newFuelPricing[i].futureFuelPricing.papMargin); |
588 | //$scope.newFuelPricing[i].futureFuelPricing.papTotal; | ||
588 | $scope.updateFutureFuelPricing.futureFuelPricingList.push({ | 589 | $scope.updateFutureFuelPricing.futureFuelPricingList.push({ |
589 | 'cost': $scope.newFuelPricing[i].futureFuelPricing.cost, | 590 | 'cost': $scope.newFuelPricing[i].futureFuelPricing.cost, |
590 | 'papMargin': $scope.newFuelPricing[i].fuelPricing.papMargin, | 591 | 'papMargin': $scope.newFuelPricing[i].futureFuelPricing.papMargin, |
591 | //'papTotal': $scope.newFuelPricing[i].futureFuelPricing.papTotal, | 592 | //'papTotal': $scope.newFuelPricing[i].futureFuelPricing.papTotal, |
592 | 'papTotal': $scope.newFuelPricing[i].futureFuelPricing.papTotal, | 593 | 'papTotal': $scope.newFuelPricing[i].futureFuelPricing.papTotal, |
593 | 'expirationDate': $scope.newFuelPricing[i].futureFuelPricing.nextExpiration, | 594 | 'expirationDate': $scope.newFuelPricing[i].futureFuelPricing.nextExpiration, |
594 | 'deployDate': $scope.newFuelPricing[i].futureFuelPricing.deployDate, | 595 | 'deployDate': $scope.newFuelPricing[i].futureFuelPricing.deployDate, |
595 | 'productId': $scope.newFuelPricing[i].id, | 596 | 'productId': $scope.newFuelPricing[i].id, |
596 | 'id': $scope.newFuelPricing[i].futureFuelPricing.id, | 597 | 'id': $scope.newFuelPricing[i].futureFuelPricing.id, |
597 | }) | 598 | }) |
598 | } | 599 | } |
599 | }else{ | 600 | }else{ |
600 | /*$scope.newFuelPricing[i].futureFuelPricing.cost = ''; | 601 | /*$scope.newFuelPricing[i].futureFuelPricing.cost = ''; |
601 | $scope.newFuelPricing[i].futureFuelPricing.papMargin = ''; | 602 | $scope.newFuelPricing[i].futureFuelPricing.papMargin = ''; |
602 | $scope.newFuelPricing[i].futureFuelPricing.papTotal = ''; | 603 | $scope.newFuelPricing[i].futureFuelPricing.papTotal = ''; |
603 | $scope.newFuelPricing[i].futureFuelPricing.expirationDate = ''; | 604 | $scope.newFuelPricing[i].futureFuelPricing.expirationDate = ''; |
604 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = '';*/ | 605 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = '';*/ |
605 | } | 606 | } |
606 | } | 607 | } |
608 | //console.log('$scope.updateFutureFuelPricing', $scope.updateFutureFuelPricing); | ||
607 | updateFuelManagerService.updateFutureFuelPricing($scope.updateFutureFuelPricing).then(function(result) { | 609 | updateFuelManagerService.updateFutureFuelPricing($scope.updateFutureFuelPricing).then(function(result) { |
608 | toastr.success('Successfully Updated', { | 610 | toastr.success('Successfully Updated', { |
609 | closeButton: true | 611 | closeButton: true |
610 | }) | 612 | }) |
611 | updateFuelManagerService.getFuelPricingNew().then(function(result) { | 613 | updateFuelManagerService.getFuelPricingNew().then(function(result) { |
612 | $scope.newFuelPricing = result; | 614 | $scope.newFuelPricing = result; |
613 | for (var i = 0; i<$scope.newFuelPricing.length; i++) { | 615 | for (var i = 0; i<$scope.newFuelPricing.length; i++) { |
614 | if ($scope.newFuelPricing[i].fuelPricing != null) { | 616 | if ($scope.newFuelPricing[i].fuelPricing != null) { |
615 | if ($scope.newFuelPricing[i].fuelPricing.expirationDate != null && $scope.newFuelPricing[i].fuelPricing.expirationDate != '') { | 617 | if ($scope.newFuelPricing[i].fuelPricing.expirationDate != null && $scope.newFuelPricing[i].fuelPricing.expirationDate != '') { |
616 | var newTime = new Date($scope.newFuelPricing[i].fuelPricing.expirationDate); | 618 | var newTime = new Date($scope.newFuelPricing[i].fuelPricing.expirationDate); |
617 | var month = newTime.getUTCMonth() + 1; //months from 1-12 | 619 | var month = newTime.getUTCMonth() + 1; //months from 1-12 |
618 | var day = newTime.getUTCDate(); | 620 | var day = newTime.getUTCDate(); |
619 | var year = newTime.getUTCFullYear(); | 621 | var year = newTime.getUTCFullYear(); |
620 | $scope.newFuelPricing[i].fuelPricing.expirationDate = month+'/'+day+'/'+year; | 622 | $scope.newFuelPricing[i].fuelPricing.expirationDate = month+'/'+day+'/'+year; |
621 | } | 623 | } |
622 | } | 624 | } |
623 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { | 625 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { |
624 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { | 626 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { |
625 | if ($scope.newFuelPricing[i].futureFuelPricing.nextExpiration != null && $scope.newFuelPricing[i].futureFuelPricing.nextExpiration != '') { | 627 | if ($scope.newFuelPricing[i].futureFuelPricing.nextExpiration != null && $scope.newFuelPricing[i].futureFuelPricing.nextExpiration != '') { |
626 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.nextExpiration); | 628 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.nextExpiration); |
627 | var nextMonth = newTime.getUTCMonth() + 1; //months from 1-12 | 629 | var nextMonth = newTime.getUTCMonth() + 1; //months from 1-12 |
628 | var nextDay = newTime.getUTCDate(); | 630 | var nextDay = newTime.getUTCDate(); |
629 | var nextYear = newTime.getUTCFullYear(); | 631 | var nextYear = newTime.getUTCFullYear(); |
630 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = nextMonth+'/'+nextDay+'/'+nextYear; | 632 | $scope.newFuelPricing[i].futureFuelPricing.nextExpiration = nextMonth+'/'+nextDay+'/'+nextYear; |
631 | } | 633 | } |
632 | } | 634 | } |
633 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { | 635 | if ($scope.newFuelPricing[i].futureFuelPricing != null) { |
634 | if ($scope.newFuelPricing[i].futureFuelPricing.deployDate != null && $scope.newFuelPricing[i].futureFuelPricing.deployDate != '') { | 636 | if ($scope.newFuelPricing[i].futureFuelPricing.deployDate != null && $scope.newFuelPricing[i].futureFuelPricing.deployDate != '') { |
635 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.deployDate); | 637 | var newTime = new Date($scope.newFuelPricing[i].futureFuelPricing.deployDate); |
636 | var dmonth = newTime.getUTCMonth() + 1; //months from 1-12 | 638 | var dmonth = newTime.getUTCMonth() + 1; //months from 1-12 |
637 | var dday = newTime.getUTCDate(); | 639 | var dday = newTime.getUTCDate(); |
638 | var dyear = newTime.getUTCFullYear(); | 640 | var dyear = newTime.getUTCFullYear(); |
639 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = dmonth+'/'+dday+'/'+dyear; | 641 | $scope.newFuelPricing[i].futureFuelPricing.deployDate = dmonth+'/'+dday+'/'+dyear; |
640 | } | 642 | } |
641 | } | 643 | } |
642 | } | 644 | } |
643 | } | 645 | } |
644 | $scope.showLoader = false; | 646 | $scope.showLoader = false; |
645 | }) | 647 | }) |
646 | }) | 648 | }) |
647 | 649 | ||
648 | 650 | ||
649 | } | 651 | } |
650 | 652 | ||
651 | updateFuelManagerService.getMargin().then(function(result) { | 653 | updateFuelManagerService.getMargin().then(function(result) { |
652 | $scope.marginList = result; | 654 | $scope.marginList = result; |
653 | }) | 655 | }) |
654 | 656 | ||
655 | $scope.marginIdDelete = ''; | 657 | $scope.marginIdDelete = ''; |
656 | $scope.deleteJetAccordian = function(id){ | 658 | $scope.deleteJetAccordian = function(id){ |
657 | $scope.marginIdDelete = id; | 659 | $scope.marginIdDelete = id; |
658 | $('#deleteMargin').css('display', 'block'); | 660 | $('#deleteMargin').css('display', 'block'); |
659 | } | 661 | } |
660 | 662 | ||
661 | $scope.confirmDeleteMargin = function(){ | 663 | $scope.confirmDeleteMargin = function(){ |
662 | $('#deleteMargin').css('display', 'none'); | 664 | $('#deleteMargin').css('display', 'none'); |
663 | $scope.showLoader = true; | 665 | $scope.showLoader = true; |
664 | updateFuelManagerService.deleteMargin($scope.marginIdDelete).then(function(result) { | 666 | updateFuelManagerService.deleteMargin($scope.marginIdDelete).then(function(result) { |
665 | toastr.success(''+result.success+'', { | 667 | toastr.success(''+result.success+'', { |
666 | closeButton: true | 668 | closeButton: true |
667 | }) | 669 | }) |
668 | updateFuelManagerService.getATypeJets($scope.userProfileId).then(function(result) { | 670 | updateFuelManagerService.getATypeJets($scope.userProfileId).then(function(result) { |
669 | $scope.aTypeJets = result; | 671 | $scope.aTypeJets = result; |
670 | $scope.showLoader = false; | 672 | $scope.showLoader = false; |
671 | }) | 673 | }) |
672 | }) | 674 | }) |
673 | } | 675 | } |
674 | 676 | ||
675 | $scope.cancelMarginDelete = function(){ | 677 | $scope.cancelMarginDelete = function(){ |
676 | $scope.marginIdDelete = ''; | 678 | $scope.marginIdDelete = ''; |
677 | $('#deleteMargin').css('display', 'none'); | 679 | $('#deleteMargin').css('display', 'none'); |
678 | } | 680 | } |
679 | 681 | ||
680 | $scope.marginVtypeIdDelete = ''; | 682 | $scope.marginVtypeIdDelete = ''; |
681 | $scope.deleteVtypeJetAccordian = function(id){ | 683 | $scope.deleteVtypeJetAccordian = function(id){ |
682 | $scope.marginVtypeIdDelete = id; | 684 | $scope.marginVtypeIdDelete = id; |
683 | $('#deleteVtypeMargin').css('display', 'block'); | 685 | $('#deleteVtypeMargin').css('display', 'block'); |
684 | } | 686 | } |
685 | 687 | ||
686 | $scope.confirmDeletVtypeMargin = function(){ | 688 | $scope.confirmDeletVtypeMargin = function(){ |
687 | $('#deleteVtypeMargin').css('display', 'none'); | 689 | $('#deleteVtypeMargin').css('display', 'none'); |
688 | $scope.showLoader = true; | 690 | $scope.showLoader = true; |
689 | updateFuelManagerService.deleteMargin($scope.marginVtypeIdDelete).then(function(result) { | 691 | updateFuelManagerService.deleteMargin($scope.marginVtypeIdDelete).then(function(result) { |
690 | toastr.success(''+result.success+'', { | 692 | toastr.success(''+result.success+'', { |
691 | closeButton: true | 693 | closeButton: true |
692 | }) | 694 | }) |
693 | updateFuelManagerService.getVTypeJets($scope.userProfileId).then(function(result) { | 695 | updateFuelManagerService.getVTypeJets($scope.userProfileId).then(function(result) { |
694 | $scope.vTypeJets = result; | 696 | $scope.vTypeJets = result; |
695 | $scope.showLoader = false; | 697 | $scope.showLoader = false; |
696 | }) | 698 | }) |
697 | }) | 699 | }) |
698 | } | 700 | } |
699 | 701 | ||
700 | $scope.cancelVtypeMarginDelete = function(){ | 702 | $scope.cancelVtypeMarginDelete = function(){ |
701 | $scope.marginVtypeIdDelete = ''; | 703 | $scope.marginVtypeIdDelete = ''; |
702 | $('#deleteVtypeMargin').css('display', 'none'); | 704 | $('#deleteVtypeMargin').css('display', 'none'); |
703 | } | 705 | } |
704 | 706 | ||
705 | 707 | ||
706 | 708 | ||
707 | 709 | ||
708 | 710 | ||
709 | }]); | 711 | }]); |
710 | 712 | ||
711 | 713 | ||
712 | 714 |
app/partials/updateFuelManager/updateFuelManager.html
1 | 1 | ||
2 | 2 | ||
3 | <style> | 3 | <style> |
4 | .subnavbar .mainnav > li:nth-child(2) > a{ | 4 | .subnavbar .mainnav > li:nth-child(2) > a{ |
5 | color: #ff9900; | 5 | color: #ff9900; |
6 | } | 6 | } |
7 | </style> | 7 | </style> |
8 | <div class="myLoader" ng-show="showLoader"> | 8 | <div class="myLoader" ng-show="showLoader"> |
9 | <img src="../img/hourglass.gif" width="50px;"> | 9 | <img src="../img/hourglass.gif" width="50px;"> |
10 | </div> | 10 | </div> |
11 | <div style="width: 90%; margin-left: 5%;"> | 11 | <div style="width: 96%; margin-left: 2%;"> |
12 | <div class="row"> | 12 | <div class="row"> |
13 | 13 | ||
14 | <div class="col-md-6"> | 14 | <div class="col-md-6"> |
15 | <div class="widget stacked"> | 15 | <div class="widget stacked"> |
16 | <div class="widget-header"> | 16 | <div class="widget-header"> |
17 | <i class="fa fa-pencil"></i> | 17 | <i class="fa fa-pencil"></i> |
18 | <h3>Price Manager Staging</h3> | 18 | <h3>Price Manager Staging</h3> |
19 | </div> | 19 | </div> |
20 | <!-- /widget-header --> | 20 | <!-- /widget-header --> |
21 | <div class="widget-content"> | 21 | <div class="widget-content"> |
22 | <h6 style="color:#F90">Queue Pricing for Deployment in the Price Manager below</h6> | 22 | <h6 style="color:#F90">Queue Pricing for Deployment in the Price Manager below</h6> |
23 | <form> | 23 | <form> |
24 | <table class="table"> | 24 | <table class="table"> |
25 | <thead> | 25 | <thead> |
26 | <tr> | 26 | <tr> |
27 | <th> Product</th> | 27 | <th> Product</th> |
28 | <th> Cost</th> | 28 | <th> Cost</th> |
29 | <th> PAP(Margin)</th> | 29 | <th> PAP(Margin)</th> |
30 | <th> Deploy Date</th> | 30 | <th> Deploy Date</th> |
31 | <th style="color: #F90;">Price Expires</th> | 31 | <th style="color: #F90;">Price Expires</th> |
32 | <th> PAP(Total)</th> | 32 | <th> PAP(Total)</th> |
33 | </tr> | 33 | </tr> |
34 | </thead> | 34 | </thead> |
35 | <tbody> | 35 | <tbody> |
36 | <tr ng-repeat="fuelPricing in newFuelPricing"> | 36 | <tr ng-repeat="fuelPricing in newFuelPricing"> |
37 | <td> | 37 | <td> |
38 | <span style="color: #2196f3" ng-show="fuelPricing.jeta">{{fuelPricing.name}}</span> | 38 | <span style="color: #2196f3" ng-show="fuelPricing.jeta">{{fuelPricing.name}}</span> |
39 | <span ng-show="fuelPricing.jeta">{{fuelPricing.namejetrest}}</span> | 39 | <span ng-show="fuelPricing.jeta">{{fuelPricing.namejetrest}}</span> |
40 | <span style="color: 39c" ng-show="fuelPricing.avgas">{{fuelPricing.name}}</span> | 40 | <span style="color: 39c" ng-show="fuelPricing.avgas">{{fuelPricing.name}}</span> |
41 | <span ng-show="fuelPricing.avgas">{{fuelPricing.nameavgasrest}}</span> | 41 | <span ng-show="fuelPricing.avgas">{{fuelPricing.nameavgasrest}}</span> |
42 | </td> | 42 | </td> |
43 | <td> | 43 | <td> |
44 | <input type="text" class="form-control" ng-model="fuelPricing.futureFuelPricing.cost" style="height:31px; width: 50px; padding: 6px 6px;"> | 44 | <input type="text" class="form-control" ng-model="fuelPricing.futureFuelPricing.cost" style="height:31px; width: 50px; padding: 6px 6px;"> |
45 | </td> | 45 | </td> |
46 | <td> | 46 | <td> |
47 | <input type="text" class="form-control" ng-model="fuelPricing.futureFuelPricing.papMargin" style="height:31px; width: 80px; padding: 6px 6px;"> | 47 | <input type="text" class="form-control" ng-model="fuelPricing.futureFuelPricing.papMargin" style="height:31px; width: 80px; padding: 6px 6px;"> |
48 | </td> | 48 | </td> |
49 | <td> | 49 | <td> |
50 | <input type="text" class="form-control" ng-disabled="fuelPricing.futureFuelPricing.cost == undefined || fuelPricing.futureFuelPricing.cost == null || fuelPricing.futureFuelPricing.cost == ''" datepicker ng-model="fuelPricing.futureFuelPricing.deployDate" style="height:31px; width: 80px; padding: 6px 6px;"> | 50 | <input type="text" class="form-control" ng-disabled="fuelPricing.futureFuelPricing.cost == undefined || fuelPricing.futureFuelPricing.cost == null || fuelPricing.futureFuelPricing.cost == ''" datepicker ng-model="fuelPricing.futureFuelPricing.deployDate" style="height:31px; width: 80px; padding: 6px 6px;"> |
51 | </td> | 51 | </td> |
52 | <td> | 52 | <td> |
53 | <input type="text" class="form-control" datepicker ng-disabled="fuelPricing.futureFuelPricing.cost == undefined || fuelPricing.futureFuelPricing.cost == null || fuelPricing.futureFuelPricing.cost == ''" ng-model="fuelPricing.futureFuelPricing.nextExpiration" style="height:31px; width: 80px; padding: 6px 6px;"> | 53 | <input type="text" class="form-control" datepicker ng-disabled="fuelPricing.futureFuelPricing.cost == undefined || fuelPricing.futureFuelPricing.cost == null || fuelPricing.futureFuelPricing.cost == ''" ng-model="fuelPricing.futureFuelPricing.nextExpiration" style="height:31px; width: 80px; padding: 6px 6px;"> |
54 | </td> | 54 | </td> |
55 | <td> | 55 | <td> |
56 | <span style="line-height: 31px; color: #1ab394;">$ {{fuelPricing.futureFuelPricing.cost -- fuelPricing.fuelPricing.papMargin | number : 2}}</span> | 56 | <span style="line-height: 31px; color: #1ab394;">$ {{fuelPricing.futureFuelPricing.cost -- fuelPricing.futureFuelPricing.papMargin | number : 2}}</span> |
57 | </td> | 57 | </td> |
58 | </tr> | 58 | </tr> |
59 | </tbody> | 59 | </tbody> |
60 | </table> | 60 | </table> |
61 | <div class="row" style="margin-left: 0px;"> | 61 | <div class="row" style="margin-left: 0px;"> |
62 | <div class="col-md-12" style= "text-align: right;"> | 62 | <div class="col-md-12" style= "text-align: right;"> |
63 | <div style="float: left;"> | 63 | <div style="float: left;"> |
64 | <button type="button" class="btn btn-primary btn-xs" ng-click="updateFutureFuelPricingClick()" style= "text-align: center; font-size:12px">Save & Deploy Immediately</button> | 64 | <button type="button" class="btn btn-primary btn-xs" ng-click="updateFutureFuelPricingClick()" style= "text-align: center; font-size:12px">Save & Deploy Immediately</button> |
65 | 65 | ||
66 | <button type="reset" class="btn btn-default btn-xs">Reset All</button> | 66 | <button type="reset" class="btn btn-default btn-xs">Reset All</button> |
67 | </div> | 67 | </div> |
68 | <div style="float: right;"> | 68 | <div style="float: right;"> |
69 | <button type="button" class="btn btn-success btn-xs" ng-click="updateFutureFuelPricingClick()" style="margin-right:3%">Save & Stage for Deploy</button> | 69 | <button type="button" class="btn btn-success btn-xs" ng-click="updateFutureFuelPricingClick()" style="margin-right:3%">Save & Stage for Deploy</button> |
70 | </div> | 70 | </div> |
71 | <div style="clear: both;"></div> | 71 | <div style="clear: both;"></div> |
72 | </div> | 72 | </div> |
73 | </div> | 73 | </div> |
74 | </form> | 74 | </form> |
75 | </div> | 75 | </div> |
76 | <!-- /widget-content --> | 76 | <!-- /widget-content --> |
77 | </div> | 77 | </div> |
78 | <!-- /widget --> | 78 | <!-- /widget --> |
79 | </div> | 79 | </div> |
80 | 80 | ||
81 | <div class="col-md-6"> | 81 | <div class="col-md-6"> |
82 | <div class="widget stacked"> | 82 | <div class="widget stacked"> |
83 | <div class="widget-header"> | 83 | <div class="widget-header"> |
84 | <i class="fa fa-pencil"></i> | 84 | <i class="fa fa-pencil"></i> |
85 | <h3 style="font-style: italic"><b style="color: #2196f3; font-style: normal">JET-A</b> Customer Margin Template</h3> | 85 | <h3 style="font-style: italic"><b style="color: #2196f3; font-style: normal">JET-A</b> Customer Margin Template</h3> |
86 | 86 | ||
87 | </div> | 87 | </div> |
88 | <!-- /widget-header --> | 88 | <!-- /widget-header --> |
89 | <div class="widget-content" style="padding-top: 10px;"> | 89 | <div class="widget-content" style="padding-top: 10px;"> |
90 | <section id="accordions"> | 90 | <section id="accordions"> |
91 | <div class="newCustomAccordian"> | 91 | <div class="newCustomAccordian"> |
92 | <!-- tab 1 --> | 92 | <!-- tab 1 --> |
93 | <div ng-repeat="jets in aTypeJets"> | 93 | <div ng-repeat="jets in aTypeJets"> |
94 | <div class="customAccordianHeader" id="{{jets.id}}"> | 94 | <div class="customAccordianHeader" id="{{jets.id}}"> |
95 | <span>{{jets.marginName}}</span> | 95 | <span>{{jets.marginName}}</span> |
96 | <select class="form-control" disabled="true" ng-model="jets.pricingStructure"> | 96 | <select class="form-control" disabled="true" ng-model="jets.pricingStructure"> |
97 | <option value="" disabled selected>Pricing Structure</option> | 97 | <option value="" disabled selected>Pricing Structure</option> |
98 | <option value="minus">Retail/PAP-(minus)</option> | 98 | <option value="minus">Retail/PAP-(minus)</option> |
99 | <option value="plus">Cost+(plus)</option> | 99 | <option value="plus">Cost+(plus)</option> |
100 | </select> | 100 | </select> |
101 | <span style="margin-right: 0;">$</span> | 101 | <span style="margin-right: 0;">$</span> |
102 | <input type="text" disabled="true" class="form-control" ng-model="jets.marginValue"> | 102 | <input type="text" disabled="true" class="form-control" ng-model="jets.marginValue"> |
103 | <div class="pull-right"> | 103 | <div class="pull-right"> |
104 | <button class="btn btn-success" style="display: none; background-image: none; background-color: #f3f3f3; color: #333; border:0;" ng-click="closeAccordian(jets)">Close</button> | 104 | <button class="btn btn-success" style="display: none; background-image: none; background-color: #f3f3f3; color: #333; border:0;" ng-click="closeAccordian(jets)">Close</button> |
105 | <button class="btn btn-success" style="display: none;" ng-click="saveJetAccordian(jets)">Save</button> | 105 | <button class="btn btn-success" style="display: none;" ng-click="saveJetAccordian(jets)">Save</button> |
106 | <button class="btn btn-danger" style="display: none;" ng-click="deleteJetAccordian(jets.id)">Delete</button> | 106 | <button class="btn btn-danger" style="display: none;" ng-click="deleteJetAccordian(jets.id)">Delete</button> |
107 | <button type="button" class="btn btn-primary" ng-model="" ng-click="emailPricingForMargin(jets.id)" style= "font-weight: normal; text-align: center; font-size:12px">Email Pricing for this Margin</button> | 107 | <button type="button" class="btn btn-primary" ng-click="emailPricingForMargin(jets.id)" style= "font-weight: normal; text-align: center; font-size:12px">Email Pricing for this Margin</button> |
108 | <button class="btn btn-default" ng-click="toggleJestAccordian(jets.id, $index)" style= "text-align: center; font-size:12px">Edit</button> | 108 | <button class="btn btn-default" ng-click="toggleJestAccordian(jets.id, $index)" style= "text-align: center; font-size:12px">Edit</button> |
109 | </div> | 109 | </div> |
110 | <div class="clearfix"></div> | 110 | <div class="clearfix"></div> |
111 | </div> | 111 | </div> |
112 | <div class="customAccordianTabBody {{jets.id}}" style="display: none;"> | 112 | <div class="customAccordianTabBody {{jets.id}}" style="display: none;"> |
113 | <div class="tierListWrap" ng-repeat="tier in aTypeJets[$index].tierList"> | 113 | <div class="tierListWrap" ng-repeat="tier in aTypeJets[$index].tierList"> |
114 | <div class="tierListHead" style="height: 36px;"> | 114 | <div class="tierListHead" style="height: 36px;"> |
115 | <span class="pull-left tierHeadingSpan" ng-hide="showEditTier">{{tier.minTierBreak}}-{{tier.maxTierBreak}} gal. | 115 | <span class="pull-left tierHeadingSpan" ng-hide="showEditTier">{{tier.minTierBreak}}-{{tier.maxTierBreak}} gal. |
116 | </span> | 116 | </span> |
117 | <i class="fa fa-pencil-square-o pull-right" ng-click="showEditTier = ! showEditTier" ng-hide="showEditTier" style="margin-top: 5px; cursor: pointer;" aria-hidden="true"></i> | 117 | <i class="fa fa-pencil-square-o pull-right" ng-click="showEditTier = ! showEditTier" ng-hide="showEditTier" style="margin-top: 5px; cursor: pointer;" aria-hidden="true"></i> |
118 | 118 | ||
119 | <input type="text" placeholder="min" style="width: 36px;" ng-model="tier.minTierBreak" ng-show="showEditTier"> | 119 | <input type="text" placeholder="min" style="width: 36px;" ng-model="tier.minTierBreak" ng-show="showEditTier"> |
120 | <span ng-show="showEditTier">-</span> | 120 | <span ng-show="showEditTier">-</span> |
121 | <input type="text" placeholder="max" style="width: 36px;" ng-model="tier.maxTierBreak" ng-show="showEditTier"> <b ng-show="showEditTier">gal.</b> | 121 | <input type="text" placeholder="max" style="width: 36px;" ng-model="tier.maxTierBreak" ng-show="showEditTier"> <b ng-show="showEditTier">gal.</b> |
122 | <div class="clearfix"></div> | 122 | <div class="clearfix"></div> |
123 | </div> | 123 | </div> |
124 | <div class="tierListBody" style="height: 35px;"> | 124 | <div class="tierListBody" style="height: 35px;"> |
125 | <span class="pull-left minTierSpan" ng-hide="showEditTier">-${{tier.margin}}</span> | 125 | <span class="pull-left minTierSpan" ng-hide="showEditTier">-${{tier.margin}}</span> |
126 | 126 | ||
127 | <input type="text" placeholder="max" style="width: 36px;" ng-model="tier.margin" ng-show="showEditTier"> | 127 | <input type="text" placeholder="max" style="width: 36px;" ng-model="tier.margin" ng-show="showEditTier"> |
128 | 128 | ||
129 | <span class="pull-right maxTierSpan" ng-hide="showEditTier">(${{tier.marginTotal | number : 2}})</span> | 129 | <span class="pull-right maxTierSpan" ng-hide="showEditTier">(${{tier.marginTotal | number : 2}})</span> |
130 | 130 | ||
131 | <button class="addTierBtn" ng-click="editTier(tier, $parent.$index)" ng-show="showEditTier">Save</button> | 131 | <button class="addTierBtn" ng-click="editTier(tier, $parent.$index)" ng-show="showEditTier">Save</button> |
132 | 132 | ||
133 | <i class="fa fa-trash-o deleteTierIcon" ng-click="deleteTier(tier.id, jets.id, $parent.$index)" aria-hidden="true" ng-show="showEditTier"></i> | 133 | <i class="fa fa-trash-o deleteTierIcon" ng-click="deleteTier(tier.id, jets.id, $parent.$index)" aria-hidden="true" ng-show="showEditTier"></i> |
134 | 134 | ||
135 | <div class="clearfix"></div> | 135 | <div class="clearfix"></div> |
136 | </div> | 136 | </div> |
137 | </div> | 137 | </div> |
138 | <div class="tierListWrap" style="width: 160px;"> | 138 | <div class="tierListWrap" style="width: 160px;"> |
139 | <div class="tierListHead" style="border-right: 1px solid #ddd;"> | 139 | <div class="tierListHead" style="border-right: 1px solid #ddd;"> |
140 | <input type="text" placeholder="min" ng-model="trData[$index].minTierBreak"> | 140 | <input type="text" placeholder="min" ng-model="trData[$index].minTierBreak"> |
141 | <span>-</span> | 141 | <span>-</span> |
142 | <input type="text" placeholder="max" ng-model="trData[$index].maxTierBreak"> <b>gal.</b> | 142 | <input type="text" placeholder="max" ng-model="trData[$index].maxTierBreak"> <b>gal.</b> |
143 | <div class="clearfix"></div> | 143 | <div class="clearfix"></div> |
144 | </div> | 144 | </div> |
145 | <div class="tierListBody" style="border-right: 1px solid #ddd;"> | 145 | <div class="tierListBody" style="border-right: 1px solid #ddd;"> |
146 | <span style="color: #449d44;">$</span> | 146 | <span style="color: #449d44;">$</span> |
147 | <input type="text" placeholder="margin" ng-model="trData[$index].margin" class="tierTextBox" style="width: 70px; height: 24px;"> | 147 | <input type="text" placeholder="margin" ng-model="trData[$index].margin" class="tierTextBox" style="width: 70px; height: 24px;"> |
148 | <button class="addTierBtn" ng-click="addNewTier(jets.id, trData, $index)">Add Tier</button> | 148 | <button class="addTierBtn" ng-click="addNewTier(jets.id, trData, $index)">Add Tier</button> |
149 | <div class="clearfix"></div> | 149 | <div class="clearfix"></div> |
150 | </div> | 150 | </div> |
151 | </div> | 151 | </div> |
152 | <!-- <div class="tierListWrap" style="width: 32px;"> | 152 | <!-- <div class="tierListWrap" style="width: 32px;"> |
153 | <div class="tierListHead" style="height: 36px; border-right: 1px solid #ddd;"> | 153 | <div class="tierListHead" style="height: 36px; border-right: 1px solid #ddd;"> |
154 | | 154 | |
155 | </div> | 155 | </div> |
156 | <div class="tierListBody" style="height: 35px; border-right: 1px solid #ddd;"> | 156 | <div class="tierListBody" style="height: 35px; border-right: 1px solid #ddd;"> |
157 | <i class="fa fa-trash-o deleteTierIcon" aria-hidden="true"></i> | 157 | <i class="fa fa-trash-o deleteTierIcon" aria-hidden="true"></i> |
158 | </div> | 158 | </div> |
159 | </div> --> | 159 | </div> --> |
160 | <div class="clearfix"></div> | 160 | <div class="clearfix"></div> |
161 | <!-- <textarea class="form-control resizeTextarea" ng-model="jets.message" placeholder="Message..."></textarea> --> | 161 | <!-- <textarea class="form-control resizeTextarea" ng-model="jets.message" placeholder="Message..."></textarea> --> |
162 | <br/> | 162 | <br/> |
163 | <div ckeditor="options" ng-model="jets.message" ready="onReady()"></div> | 163 | <div ckeditor="options" ng-model="jets.message" ready="onReady()"></div> |
164 | </div> | 164 | </div> |
165 | </div> | 165 | </div> |
166 | </div> | 166 | </div> |
167 | <div class="pull-right"> | 167 | <div class="pull-right"> |
168 | <button type="submit" class="btn btn-success btn-sm" ng-click="addNewMarginBtn()" style="margin-top: 4px; margin-right: 10px;"><i class="fa fa-plus" aria-hidden="true"></i> Add New Margin</button> | 168 | <button type="submit" class="btn btn-success btn-sm" ng-click="addNewMarginBtn()" style="margin-top: 4px; margin-right: 10px;"><i class="fa fa-plus" aria-hidden="true"></i> Add New Margin</button> |
169 | </div> | 169 | </div> |
170 | </section> | 170 | </section> |
171 | <!-- <div class="row"> </div> | 171 | <!-- <div class="row"> </div> |
172 | <div class="row"> | 172 | <div class="row"> |
173 | <div class="form-group"> | 173 | <div class="form-group"> |
174 | <div class="col-lg-12 text-right"> | 174 | <div class="col-lg-12 text-right"> |
175 | <button type="submit" class="btn btn-success"><i class="icon-ok"></i> Save Form</button> | 175 | <button type="submit" class="btn btn-success"><i class="icon-ok"></i> Save Form</button> |
176 | <button type="reset" class="btn btn-default">Cancel</button> | 176 | <button type="reset" class="btn btn-default">Cancel</button> |
177 | </div> | 177 | </div> |
178 | </div> | 178 | </div> |
179 | </div> --> | 179 | </div> --> |
180 | </div> | 180 | </div> |
181 | <!-- /widget-content --> | 181 | <!-- /widget-content --> |
182 | </div> | 182 | </div> |
183 | <!-- /widget --> | 183 | <!-- /widget --> |
184 | </div> | 184 | </div> |
185 | <!-- /span6 --> | 185 | <!-- /span6 --> |
186 | </div> | 186 | </div> |
187 | <!-- /span12 --> | 187 | <!-- /span12 --> |
188 | </div> | 188 | </div> |
189 | <!-- /row --> | 189 | <!-- /row --> |
190 | <div style="width: 90%; margin-left: 5%;"> | 190 | <div style="width: 90%; margin-left: 5%;"> |
191 | <div class="row"> | 191 | <div class="row"> |
192 | <div class="col-md-6"> | 192 | <div class="col-md-6"> |
193 | <div class="widget stacked"> | 193 | <div class="widget stacked"> |
194 | <div class="widget-header"> | 194 | <div class="widget-header"> |
195 | <i class="fa fa-pencil"></i> | 195 | <i class="fa fa-pencil"></i> |
196 | <h3>Price Manager</h3> | 196 | <h3>Price Manager</h3> |
197 | <select style="float: right; margin: 7px 10px; width: 150px; height: 26px; padding: 0 0;" class="btn btn-primary" class="form-control" ng-model="sendEmail.pricing" ng-change="confirmMail()"> | 197 | <select style="float: right; margin: 7px 10px; width: 150px; height: 26px; padding: 0 0;" class="btn btn-primary" class="form-control" ng-model="sendEmail.pricing" ng-change="confirmMail()"> |
198 | <option value="" disabled selected="selected">Email All Pricing</option> | 198 | <option value="" disabled selected="selected">Email All Pricing</option> |
199 | <option value="JET-A">Email JET-A pricing only</option> | 199 | <option value="JET-A">Email JET-A pricing only</option> |
200 | <option value="AVGAS">Email AVGAS pricing only</option> | 200 | <option value="AVGAS">Email AVGAS pricing only</option> |
201 | <option disabled>_______________________________</option> | 201 | <option disabled>_______________________________</option> |
202 | <option value="all">Distribute All</option> | 202 | <option value="all">Distribute All</option> |
203 | </select> | 203 | </select> |
204 | </div> | 204 | </div> |
205 | <!-- /widget-header --> | 205 | <!-- /widget-header --> |
206 | <div class="widget-content"> | 206 | <div class="widget-content"> |
207 | <h4>Update Fuel Price Here</h4> | 207 | <h4>Update Fuel Price Here</h4> |
208 | <table class="table"> | 208 | <table class="table"> |
209 | <thead> | 209 | <thead> |
210 | <tr> | 210 | <tr> |
211 | <th> Product</th> | 211 | <th> Product</th> |
212 | <th> Cost</th> | 212 | <th> Cost</th> |
213 | <th> Margin</th> | 213 | <th> Margin</th> |
214 | <th> PAP(Total)</th> | 214 | <th> PAP(Total)</th> |
215 | <th style="color: #F90;"> Expires</th> | 215 | <th style="color: #F90;"> Expires</th> |
216 | </tr> | 216 | </tr> |
217 | </thead> | 217 | </thead> |
218 | <tbody> | 218 | <tbody> |
219 | <tr ng-repeat="fuelPricing in newFuelPricing"> | 219 | <tr ng-repeat="fuelPricing in newFuelPricing"> |
220 | <td> | 220 | <td> |
221 | <span style="color: #2196f3" ng-show="fuelPricing.jeta">{{fuelPricing.name}}</span> | 221 | <span style="color: #2196f3" ng-show="fuelPricing.jeta">{{fuelPricing.name}}</span> |
222 | <span ng-show="fuelPricing.jeta">{{fuelPricing.namejetrest}}</span> | 222 | <span ng-show="fuelPricing.jeta">{{fuelPricing.namejetrest}}</span> |
223 | <span style="color: 39c" ng-show="fuelPricing.avgas">{{fuelPricing.name}}</span> | 223 | <span style="color: 39c" ng-show="fuelPricing.avgas">{{fuelPricing.name}}</span> |
224 | <span ng-show="fuelPricing.avgas">{{fuelPricing.nameavgasrest}}</span> | 224 | <span ng-show="fuelPricing.avgas">{{fuelPricing.nameavgasrest}}</span> |
225 | </td> | 225 | </td> |
226 | <td> | 226 | <td> |
227 | <span>{{fuelPricing.fuelPricing.cost}}</span> | 227 | <span>{{fuelPricing.fuelPricing.cost}}</span> |
228 | </td> | 228 | </td> |
229 | <td> | 229 | <td> |
230 | <span>{{fuelPricing.fuelPricing.papMargin}}</span> | 230 | <span>{{fuelPricing.fuelPricing.papMargin}}</span> |
231 | </td> | 231 | </td> |
232 | <td> | 232 | <td> |
233 | <span style="line-height: 31px; color: #1ab394;">$ {{fuelPricing.fuelPricing.cost -- fuelPricing.fuelPricing.papMargin | number : 2 }}</span> | 233 | <span style="line-height: 31px; color: #1ab394;">$ {{fuelPricing.fuelPricing.cost -- fuelPricing.fuelPricing.papMargin | number : 2 }}</span> |
234 | </td> | 234 | </td> |
235 | <td> | 235 | <td> |
236 | <span>{{fuelPricing.fuelPricing.expirationDate}}</span> | 236 | <span>{{fuelPricing.fuelPricing.expirationDate}}</span> |
237 | </td> | 237 | </td> |
238 | </tr> | 238 | </tr> |
239 | </tbody> | 239 | </tbody> |
240 | </table> | 240 | </table> |
241 | <div class="row" style="margin-left: 0px;"> | 241 | <div class="row" style="margin-left: 0px;"> |
242 | <div class="col-md-12" style= "text-align: right;"> | 242 | <div class="col-md-12" style= "text-align: right;"> |
243 | <button type="button" class="btn btn-success btn-xs" ng-click="updateFuelPricingClick()" style="font-size:14px; margin-right:3%">Save</button> | 243 | <button type="button" class="btn btn-success btn-xs" ng-click="updateFuelPricingClick()" style="font-size:14px; margin-right:3%">Save</button> |
244 | </div> | 244 | </div> |
245 | </div> | 245 | </div> |
246 | </div> | 246 | </div> |
247 | <!-- /widget-content --> | 247 | <!-- /widget-content --> |
248 | </div> | 248 | </div> |
249 | <!-- /widget --> | 249 | <!-- /widget --> |
250 | </div> | 250 | </div> |
251 | 251 | ||
252 | <div class="col-md-6"> | 252 | <div class="col-md-6"> |
253 | <div class="widget stacked"> | 253 | <div class="widget stacked"> |
254 | <div class="widget-header"> | 254 | <div class="widget-header"> |
255 | <i class="fa fa-pencil"></i> | 255 | <i class="fa fa-pencil"></i> |
256 | <h3><b style="color: 39c;">AVGAS 100LL </b> <i>Customer Margin Template</i></h3> | 256 | <h3><b style="color: 39c;">AVGAS 100LL </b> <i>Customer Margin Template</i></h3> |
257 | 257 | ||
258 | </div> | 258 | </div> |
259 | <!-- /widget-header --> | 259 | <!-- /widget-header --> |
260 | <div class="widget-content" style="padding-top: 10px;"> | 260 | <div class="widget-content" style="padding-top: 10px;"> |
261 | <section id="accordions"> | 261 | <section id="accordions"> |
262 | <div class="newCustomAccordian"> | 262 | <div class="newCustomAccordian"> |
263 | <!-- tab 1 --> | 263 | <!-- tab 1 --> |
264 | <div ng-repeat="jets in vTypeJets"> | 264 | <div ng-repeat="jets in vTypeJets"> |
265 | <div class="customAccordianHeader" id="{{jets.id}}"> | 265 | <div class="customAccordianHeader" id="{{jets.id}}"> |
266 | <span>{{jets.marginName}}</span> | 266 | <span>{{jets.marginName}}</span> |
267 | <select class="form-control" disabled="true" ng-model="jets.pricingStructure"> | 267 | <select class="form-control" disabled="true" ng-model="jets.pricingStructure"> |
268 | <option value="" disabled selected>Pricing Structure</option> | 268 | <option value="" disabled selected>Pricing Structure</option> |
269 | <option value="minus">Retail-(minus)</option> | 269 | <option value="minus">Retail-(minus)</option> |
270 | <option value="plus">Cost+(plus)</option> | 270 | <option value="plus">Cost+(plus)</option> |
271 | <option value="equal">Direct=(equal)</option> | 271 | <option value="equal">Direct=(equal)</option> |
272 | </select> | 272 | </select> |
273 | <span style="margin-right: 0;">$</span> | 273 | <span style="margin-right: 0;">$</span> |
274 | <input type="text" disabled="true" class="form-control" ng-model="jets.marginValue"> | 274 | <input type="text" disabled="true" class="form-control" ng-model="jets.marginValue"> |
275 | <div class="pull-right"> | 275 | <div class="pull-right"> |
276 | <button class="btn btn-success" style="display: none; background-image: none; background-color: #f3f3f3; color: #333; border:0;" ng-click="closeAccordianVtype(jets)">Close</button> | 276 | <button class="btn btn-success" style="display: none; background-image: none; background-color: #f3f3f3; color: #333; border:0;" ng-click="closeAccordianVtype(jets)">Close</button> |
277 | <button class="btn btn-success" style="display: none;" ng-click="saveVtypeJetAccordian(jets)">Save</button> | 277 | <button class="btn btn-success" style="display: none;" ng-click="saveVtypeJetAccordian(jets)">Save</button> |
278 | <button class="btn btn-danger" style="display: none;" ng-click="deleteVtypeJetAccordian(jets.id)">Delete</button> | 278 | <button class="btn btn-danger" style="display: none;" ng-click="deleteVtypeJetAccordian(jets.id)">Delete</button> |
279 | <button type="button" class="btn btn-primary" ng-click="emailPricingForMargin()" style= "font-weight: normal; text-align: center; font-size:12px">Email Pricing for this Margin</button> | 279 | <button type="button" class="btn btn-primary" ng-click="emailPricingForMargin()" style= "font-weight: normal; text-align: center; font-size:12px">Email Pricing for this Margin</button> |
280 | <button class="btn btn-default" ng-click="toggleVtypeJestAccordian(jets.id, $index)" style= "text-align: center; font-size:12px">Edit</button> | 280 | <button class="btn btn-default" ng-click="toggleVtypeJestAccordian(jets.id, $index)" style= "text-align: center; font-size:12px">Edit</button> |
281 | </div> | 281 | </div> |
282 | <div class="clearfix"></div> | 282 | <div class="clearfix"></div> |
283 | </div> | 283 | </div> |
284 | <div class="customAccordianTabBody {{jets.id}}" style="display: none;"> | 284 | <div class="customAccordianTabBody {{jets.id}}" style="display: none;"> |
285 | <div class="tierListWrap" ng-repeat="tier in vTypeJets[$index].tierList"> | 285 | <div class="tierListWrap" ng-repeat="tier in vTypeJets[$index].tierList"> |
286 | <div class="tierListHead" style="height: 36px;"> | 286 | <div class="tierListHead" style="height: 36px;"> |
287 | <span class="pull-left tierHeadingSpan" ng-hide="showEditTier">{{tier.minTierBreak}}-{{tier.maxTierBreak}} gal. | 287 | <span class="pull-left tierHeadingSpan" ng-hide="showEditTier">{{tier.minTierBreak}}-{{tier.maxTierBreak}} gal. |
288 | </span> | 288 | </span> |
289 | <i class="fa fa-pencil-square-o pull-right" ng-click="showEditTier = ! showEditTier" ng-hide="showEditTier" style="margin-top: 5px; cursor: pointer;" aria-hidden="true"></i> | 289 | <i class="fa fa-pencil-square-o pull-right" ng-click="showEditTier = ! showEditTier" ng-hide="showEditTier" style="margin-top: 5px; cursor: pointer;" aria-hidden="true"></i> |
290 | 290 | ||
291 | <input type="text" placeholder="min" style="width: 36px;" ng-model="tier.minTierBreak" ng-show="showEditTier"> | 291 | <input type="text" placeholder="min" style="width: 36px;" ng-model="tier.minTierBreak" ng-show="showEditTier"> |
292 | <span ng-show="showEditTier">-</span> | 292 | <span ng-show="showEditTier">-</span> |
293 | <input type="text" placeholder="max" style="width: 36px;" ng-model="tier.maxTierBreak" ng-show="showEditTier"> <b ng-show="showEditTier">gal.</b> | 293 | <input type="text" placeholder="max" style="width: 36px;" ng-model="tier.maxTierBreak" ng-show="showEditTier"> <b ng-show="showEditTier">gal.</b> |
294 | <div class="clearfix"></div> | 294 | <div class="clearfix"></div> |
295 | </div> | 295 | </div> |
296 | <div class="tierListBody" style="height: 35px;"> | 296 | <div class="tierListBody" style="height: 35px;"> |
297 | <span class="pull-left minTierSpan" ng-hide="showEditTier">-${{tier.margin}}</span> | 297 | <span class="pull-left minTierSpan" ng-hide="showEditTier">-${{tier.margin}}</span> |
298 | 298 | ||
299 | <input type="text" placeholder="max" style="width: 36px;" ng-model="tier.margin" ng-show="showEditTier"> | 299 | <input type="text" placeholder="max" style="width: 36px;" ng-model="tier.margin" ng-show="showEditTier"> |
300 | 300 | ||
301 | <span class="pull-right maxTierSpan" ng-hide="showEditTier">(${{tier.marginTotal | number : 2}})</span> | 301 | <span class="pull-right maxTierSpan" ng-hide="showEditTier">(${{tier.marginTotal | number : 2}})</span> |
302 | 302 | ||
303 | <button class="addTierBtn" ng-click="editVtypeTier(tier, $parent.$index)" ng-show="showEditTier">Save</button> | 303 | <button class="addTierBtn" ng-click="editVtypeTier(tier, $parent.$index)" ng-show="showEditTier">Save</button> |
304 | 304 | ||
305 | <i class="fa fa-trash-o deleteTierIcon" ng-click="deleteVtypeTier(tier.id, jets.id, $parent.$index)" aria-hidden="true" ng-show="showEditTier"></i> | 305 | <i class="fa fa-trash-o deleteTierIcon" ng-click="deleteVtypeTier(tier.id, jets.id, $parent.$index)" aria-hidden="true" ng-show="showEditTier"></i> |
306 | 306 | ||
307 | <div class="clearfix"></div> | 307 | <div class="clearfix"></div> |
308 | </div> | 308 | </div> |
309 | </div> | 309 | </div> |
310 | <div class="tierListWrap" style="width: 160px;"> | 310 | <div class="tierListWrap" style="width: 160px;"> |
311 | <div class="tierListHead" style="border-right: 1px solid #ddd;"> | 311 | <div class="tierListHead" style="border-right: 1px solid #ddd;"> |
312 | <input type="text" placeholder="min" ng-model="vtrData[$index].minTierBreak"> | 312 | <input type="text" placeholder="min" ng-model="vtrData[$index].minTierBreak"> |
313 | <span>-</span> | 313 | <span>-</span> |
314 | <input type="text" placeholder="max" ng-model="vtrData[$index].maxTierBreak"> <b>gal.</b> | 314 | <input type="text" placeholder="max" ng-model="vtrData[$index].maxTierBreak"> <b>gal.</b> |
315 | <div class="clearfix"></div> | 315 | <div class="clearfix"></div> |
316 | </div> | 316 | </div> |
317 | <div class="tierListBody" style="border-right: 1px solid #ddd;"> | 317 | <div class="tierListBody" style="border-right: 1px solid #ddd;"> |
318 | <span style="color: #449d44;">$</span> | 318 | <span style="color: #449d44;">$</span> |
319 | <input type="text" placeholder="margin" ng-model="vtrData[$index].margin" class="tierTextBox" style="width: 70px; height: 24px;"> | 319 | <input type="text" placeholder="margin" ng-model="vtrData[$index].margin" class="tierTextBox" style="width: 70px; height: 24px;"> |
320 | <button class="addTierBtn" ng-click="addNewVtypeTier(jets.id, vtrData, $index)">Add Tier</button> | 320 | <button class="addTierBtn" ng-click="addNewVtypeTier(jets.id, vtrData, $index)">Add Tier</button> |
321 | <div class="clearfix"></div> | 321 | <div class="clearfix"></div> |
322 | </div> | 322 | </div> |
323 | </div> | 323 | </div> |
324 | <!-- <div class="tierListWrap" style="width: 32px;"> | 324 | <!-- <div class="tierListWrap" style="width: 32px;"> |
325 | <div class="tierListHead" style="height: 36px; border-right: 1px solid #ddd;"> | 325 | <div class="tierListHead" style="height: 36px; border-right: 1px solid #ddd;"> |
326 | | 326 | |
327 | </div> | 327 | </div> |
328 | <div class="tierListBody" style="height: 35px; border-right: 1px solid #ddd;"> | 328 | <div class="tierListBody" style="height: 35px; border-right: 1px solid #ddd;"> |
329 | <i class="fa fa-trash-o deleteTierIcon" aria-hidden="true"></i> | 329 | <i class="fa fa-trash-o deleteTierIcon" aria-hidden="true"></i> |
330 | </div> | 330 | </div> |
331 | </div> --> | 331 | </div> --> |
332 | <div class="clearfix"></div> | 332 | <div class="clearfix"></div> |
333 | <br/> | 333 | <br/> |
334 | <div ckeditor="options" ng-model="jets.message" ready="onReady()"></div> | 334 | <div ckeditor="options" ng-model="jets.message" ready="onReady()"></div> |
335 | </div> | 335 | </div> |
336 | </div> | 336 | </div> |
337 | </div> | 337 | </div> |
338 | <div class="pull-right"> | 338 | <div class="pull-right"> |
339 | <button type="submit" class="btn btn-success btn-sm" ng-click="addNewVtypePop()" style="margin-top: 4px; margin-right: 10px;"><i class="fa fa-plus" aria-hidden="true"></i> Add New Margin</button> | 339 | <button type="submit" class="btn btn-success btn-sm" ng-click="addNewVtypePop()" style="margin-top: 4px; margin-right: 10px;"><i class="fa fa-plus" aria-hidden="true"></i> Add New Margin</button> |
340 | </div> | 340 | </div> |
341 | </section> | 341 | </section> |
342 | <!-- <div class="row"> </div> | 342 | <!-- <div class="row"> </div> |
343 | <div class="row"> | 343 | <div class="row"> |
344 | <div class="form-group"> | 344 | <div class="form-group"> |
345 | <div class="col-lg-12 text-right"> | 345 | <div class="col-lg-12 text-right"> |
346 | <button type="submit" class="btn btn-success"><i class="icon-ok"></i> Save Form</button> | 346 | <button type="submit" class="btn btn-success"><i class="icon-ok"></i> Save Form</button> |
347 | <button type="reset" class="btn btn-default">Cancel</button> | 347 | <button type="reset" class="btn btn-default">Cancel</button> |
348 | </div> | 348 | </div> |
349 | </div> | 349 | </div> |
350 | </div> --> | 350 | </div> --> |
351 | </div> | 351 | </div> |
352 | <!-- /widget-content --> | 352 | <!-- /widget-content --> |
353 | </div> | 353 | </div> |
354 | <!-- /widget --> | 354 | <!-- /widget --> |
355 | </div> | 355 | </div> |
356 | <!-- /span6 --> | 356 | <!-- /span6 --> |
357 | </div> | 357 | </div> |
358 | </div> | 358 | </div> |
359 | </div> | 359 | </div> |
360 | </div> <!-- /container --> | 360 | </div> <!-- /container --> |
361 | 361 | ||
362 | 362 | ||
363 | <div class="addNewMargin" style="display: none;"> | 363 | <div class="addNewMargin" style="display: none;"> |
364 | <div class="customBackdrop"> | 364 | <div class="customBackdrop"> |
365 | <div class="customModalInner" style="max-width: 700px;"> | 365 | <div class="customModalInner" style="max-width: 700px;"> |
366 | <div class="customModelHead"> | 366 | <div class="customModelHead"> |
367 | <p class="pull-left"> | 367 | <p class="pull-left"> |
368 | <i class="fa fa-list-alt" aria-hidden="true"></i> | 368 | <i class="fa fa-list-alt" aria-hidden="true"></i> |
369 | Add New JET-A Customer Margin | 369 | Add New JET-A Customer Margin |
370 | </p> | 370 | </p> |
371 | <p class="pull-right"> | 371 | <p class="pull-right"> |
372 | <i class="fa fa-times" aria-hidden="true" style="cursor: pointer;" ng-click="closeMarginPopup()"></i> | 372 | <i class="fa fa-times" aria-hidden="true" style="cursor: pointer;" ng-click="closeMarginPopup()"></i> |
373 | </p> | 373 | </p> |
374 | <div class="clearfix"></div> | 374 | <div class="clearfix"></div> |
375 | </div> | 375 | </div> |
376 | <div class="customModelBody"> | 376 | <div class="customModelBody"> |
377 | 377 | ||
378 | <div class="customAccordianHeader customActive"> | 378 | <div class="customAccordianHeader customActive"> |
379 | <input type="text" class="form-control" style="width: 120px; margin-right: 10px;" placeholder="Margin Name" ng-model="newJet.marginName"> | 379 | <input type="text" class="form-control" style="width: 120px; margin-right: 10px;" placeholder="Margin Name" ng-model="newJet.marginName"> |
380 | <select class="form-control" ng-model="newJet.pricingStructure"> | 380 | <select class="form-control" ng-model="newJet.pricingStructure"> |
381 | <option value="" disabled selected>Pricing Structure</option> | 381 | <option value="" disabled selected>Pricing Structure</option> |
382 | <option value="minus">Retail-(minus)</option> | 382 | <option value="minus">Retail-(minus)</option> |
383 | <option value="plus">Cost+(plus)</option> | 383 | <option value="plus">Cost+(plus)</option> |
384 | <option value="equal">Direct=(equal)</option> | 384 | <option value="equal">Direct=(equal)</option> |
385 | </select> | 385 | </select> |
386 | <span style="margin-right: 0;">$</span> | 386 | <span style="margin-right: 0;">$</span> |
387 | <input type="text" class="form-control" style="width: 120px;" placeholder="Margin Price" ng-model="newJet.marginValue"> | 387 | <input type="text" class="form-control" style="width: 120px;" placeholder="Margin Price" ng-model="newJet.marginValue"> |
388 | <div class="clearfix"></div> | 388 | <div class="clearfix"></div> |
389 | </div> | 389 | </div> |
390 | <div class="customAccordianTabBody"> | 390 | <div class="customAccordianTabBody"> |
391 | <div ckeditor="options" ng-model="newJet.message" ready="onReady()"></div> | 391 | <div ckeditor="options" ng-model="newJet.message" ready="onReady()"></div> |
392 | </div> | 392 | </div> |
393 | 393 | ||
394 | </div> | 394 | </div> |
395 | <div class="customModelFooter text-center"> | 395 | <div class="customModelFooter text-center"> |
396 | <input type="submit" value="Save" class="btn" ng-click="addNewATypeJet()"> | 396 | <input type="submit" value="Save" class="btn" ng-click="addNewATypeJet()"> |
397 | <button class="btn" ng-click="closeMarginPopup()">Cancel</button> | 397 | <button class="btn" ng-click="closeMarginPopup()">Cancel</button> |
398 | </div> | 398 | </div> |
399 | </div> | 399 | </div> |
400 | </div> | 400 | </div> |
401 | </div> | 401 | </div> |
402 | 402 | ||
403 | <div class="addNewVtype" style="display: none;"> | 403 | <div class="addNewVtype" style="display: none;"> |
404 | <div class="customBackdrop"> | 404 | <div class="customBackdrop"> |
405 | <div class="customModalInner" style="max-width: 700px;"> | 405 | <div class="customModalInner" style="max-width: 700px;"> |
406 | <div class="customModelHead"> | 406 | <div class="customModelHead"> |
407 | <p class="pull-left"> | 407 | <p class="pull-left"> |
408 | <i class="fa fa-list-alt" aria-hidden="true"></i> | 408 | <i class="fa fa-list-alt" aria-hidden="true"></i> |
409 | Add New AVGAS 100LL Customer Margin Template | 409 | Add New AVGAS 100LL Customer Margin Template |
410 | </p> | 410 | </p> |
411 | <p class="pull-right"> | 411 | <p class="pull-right"> |
412 | <i class="fa fa-times" aria-hidden="true" style="cursor: pointer;" ng-click="closeNewVtypePop()"></i> | 412 | <i class="fa fa-times" aria-hidden="true" style="cursor: pointer;" ng-click="closeNewVtypePop()"></i> |
413 | </p> | 413 | </p> |
414 | <div class="clearfix"></div> | 414 | <div class="clearfix"></div> |
415 | </div> | 415 | </div> |
416 | <div class="customModelBody"> | 416 | <div class="customModelBody"> |
417 | 417 | ||
418 | <div class="customAccordianHeader customActive"> | 418 | <div class="customAccordianHeader customActive"> |
419 | <input type="text" class="form-control" style="width: 120px; margin-right: 10px;" placeholder="Margin Name" ng-model="newVtypeJet.marginName"> | 419 | <input type="text" class="form-control" style="width: 120px; margin-right: 10px;" placeholder="Margin Name" ng-model="newVtypeJet.marginName"> |
420 | <select class="form-control" ng-model="newVtypeJet.pricingStructure"> | 420 | <select class="form-control" ng-model="newVtypeJet.pricingStructure"> |
421 | <option value="" disabled selected>Pricing Structure</option> | 421 | <option value="" disabled selected>Pricing Structure</option> |
422 | <option value="minus">Retail-(minus)</option> | 422 | <option value="minus">Retail-(minus)</option> |
423 | <option value="plus">Cost+(plus)</option> | 423 | <option value="plus">Cost+(plus)</option> |
424 | <option value="equal">Direct=(equal)</option> | 424 | <option value="equal">Direct=(equal)</option> |
425 | </select> | 425 | </select> |
426 | <span style="margin-right: 0;">$</span> | 426 | <span style="margin-right: 0;">$</span> |
427 | <input type="text" class="form-control" style="width: 120px;" placeholder="Margin Price" ng-model="newVtypeJet.marginValue"> | 427 | <input type="text" class="form-control" style="width: 120px;" placeholder="Margin Price" ng-model="newVtypeJet.marginValue"> |
428 | <div class="clearfix"></div> | 428 | <div class="clearfix"></div> |
429 | </div> | 429 | </div> |
430 | <div class="customAccordianTabBody"> | 430 | <div class="customAccordianTabBody"> |
431 | <div ckeditor="options" ng-model="newVtypeJet.message" ready="onReady()"></div> | 431 | <div ckeditor="options" ng-model="newVtypeJet.message" ready="onReady()"></div> |
432 | </div> | 432 | </div> |
433 | 433 | ||
434 | </div> | 434 | </div> |
435 | <div class="customModelFooter text-center"> | 435 | <div class="customModelFooter text-center"> |
436 | <input type="submit" value="Save" class="btn" ng-click="addNewVTypeJet()"> | 436 | <input type="submit" value="Save" class="btn" ng-click="addNewVTypeJet()"> |
437 | <button class="btn" ng-click="closeNewVtypePop()">Cancel</button> | 437 | <button class="btn" ng-click="closeNewVtypePop()">Cancel</button> |
438 | </div> | 438 | </div> |
439 | </div> | 439 | </div> |
440 | </div> | 440 | </div> |
441 | </div> | 441 | </div> |
442 | 442 | ||
443 | <div class="customConfirmPopBackdrop" id="confirm1" style="display: none;"> | 443 | <div class="customConfirmPopBackdrop" id="confirm1" style="display: none;"> |
444 | <div class="customModalInner"> | 444 | <div class="customModalInner"> |
445 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> | 445 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> |
446 | <table> | 446 | <table> |
447 | <tr> | 447 | <tr> |
448 | <td> | 448 | <td> |
449 | <img src="img/info.png" style="width: 50px;"> | 449 | <img src="img/info.png" style="width: 50px;"> |
450 | </td> | 450 | </td> |
451 | <td> | 451 | <td> |
452 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to email pricing to everyone in your contact list?</p> | 452 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to email pricing to everyone in your contact list?</p> |
453 | </td> | 453 | </td> |
454 | </tr> | 454 | </tr> |
455 | </table> | 455 | </table> |
456 | </div> | 456 | </div> |
457 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> | 457 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> |
458 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="saveAndCloseConfirm()">Yes</button> | 458 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="saveAndCloseConfirm()">Yes</button> |
459 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelAndCloseConfirm()">Cancel</button> | 459 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelAndCloseConfirm()">Cancel</button> |
460 | </div> | 460 | </div> |
461 | </div> | 461 | </div> |
462 | </div> | 462 | </div> |
463 | <div class="customConfirmPopBackdrop" id="confirm2" style="display: none;"> | 463 | <div class="customConfirmPopBackdrop" id="confirm2" style="display: none;"> |
464 | <div class="customModalInner"> | 464 | <div class="customModalInner"> |
465 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> | 465 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> |
466 | <table> | 466 | <table> |
467 | <tr> | 467 | <tr> |
468 | <td> | 468 | <td> |
469 | <img src="img/info.png" style="width: 50px;"> | 469 | <img src="img/info.png" style="width: 50px;"> |
470 | </td> | 470 | </td> |
471 | <td> | 471 | <td> |
472 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to email pricing for this margin?</p> | 472 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to email pricing for this margin?</p> |
473 | </td> | 473 | </td> |
474 | </tr> | 474 | </tr> |
475 | </table> | 475 | </table> |
476 | </div> | 476 | </div> |
477 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> | 477 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> |
478 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="saveAndCloseForMarginConfirm()">Yes</button> | 478 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="saveAndCloseForMarginConfirm()">Yes</button> |
479 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelAndCloseForMarginConfirm()">Cancel</button> | 479 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelAndCloseForMarginConfirm()">Cancel</button> |
480 | </div> | 480 | </div> |
481 | </div> | 481 | </div> |
482 | </div> | 482 | </div> |
483 | 483 | ||
484 | <div class="customConfirmPopBackdrop" id="deleteTierConfirm" style="display: none;"> | 484 | <div class="customConfirmPopBackdrop" id="deleteTierConfirm" style="display: none;"> |
485 | <div class="customModalInner"> | 485 | <div class="customModalInner"> |
486 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> | 486 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> |
487 | <table> | 487 | <table> |
488 | <tr> | 488 | <tr> |
489 | <td> | 489 | <td> |
490 | <img src="img/info.png" style="width: 50px;"> | 490 | <img src="img/info.png" style="width: 50px;"> |
491 | </td> | 491 | </td> |
492 | <td> | 492 | <td> |
493 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to delete this Tier ?</p> | 493 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to delete this Tier ?</p> |
494 | </td> | 494 | </td> |
495 | </tr> | 495 | </tr> |
496 | </table> | 496 | </table> |
497 | </div> | 497 | </div> |
498 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> | 498 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> |
499 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="confirmDeleteTier()">Yes</button> | 499 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="confirmDeleteTier()">Yes</button> |
500 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelTierDelete()">Cancel</button> | 500 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelTierDelete()">Cancel</button> |
501 | </div> | 501 | </div> |
502 | </div> | 502 | </div> |
503 | </div> | 503 | </div> |
504 | 504 | ||
505 | <div class="customConfirmPopBackdrop" id="deleteVtypeTierConfirm" style="display: none;"> | 505 | <div class="customConfirmPopBackdrop" id="deleteVtypeTierConfirm" style="display: none;"> |
506 | <div class="customModalInner"> | 506 | <div class="customModalInner"> |
507 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> | 507 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> |
508 | <table> | 508 | <table> |
509 | <tr> | 509 | <tr> |
510 | <td> | 510 | <td> |
511 | <img src="img/info.png" style="width: 50px;"> | 511 | <img src="img/info.png" style="width: 50px;"> |
512 | </td> | 512 | </td> |
513 | <td> | 513 | <td> |
514 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to delete this Tier ?</p> | 514 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to delete this Tier ?</p> |
515 | </td> | 515 | </td> |
516 | </tr> | 516 | </tr> |
517 | </table> | 517 | </table> |
518 | </div> | 518 | </div> |
519 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> | 519 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> |
520 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="confirmDeleteVtypeTier()">Yes</button> | 520 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="confirmDeleteVtypeTier()">Yes</button> |
521 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelVtypeTierDelete()">Cancel</button> | 521 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelVtypeTierDelete()">Cancel</button> |
522 | </div> | 522 | </div> |
523 | </div> | 523 | </div> |
524 | </div> | 524 | </div> |
525 | 525 | ||
526 | <div class="customConfirmPopBackdrop" id="deleteMargin" style="display: none;"> | 526 | <div class="customConfirmPopBackdrop" id="deleteMargin" style="display: none;"> |
527 | <div class="customModalInner"> | 527 | <div class="customModalInner"> |
528 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> | 528 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> |
529 | <table> | 529 | <table> |
530 | <tr> | 530 | <tr> |
531 | <td> | 531 | <td> |
532 | <img src="img/info.png" style="width: 50px;"> | 532 | <img src="img/info.png" style="width: 50px;"> |
533 | </td> | 533 | </td> |
534 | <td> | 534 | <td> |
535 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to delete this Margin Template ?</p> | 535 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to delete this Margin Template ?</p> |
536 | </td> | 536 | </td> |
537 | </tr> | 537 | </tr> |
538 | </table> | 538 | </table> |
539 | </div> | 539 | </div> |
540 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> | 540 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> |
541 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="confirmDeleteMargin()">Yes</button> | 541 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="confirmDeleteMargin()">Yes</button> |
542 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelMarginDelete()">Cancel</button> | 542 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelMarginDelete()">Cancel</button> |
543 | </div> | 543 | </div> |
544 | </div> | 544 | </div> |
545 | </div> | 545 | </div> |
546 | 546 | ||
547 | <div class="customConfirmPopBackdrop" id="deleteVtypeMargin" style="display: none;"> | 547 | <div class="customConfirmPopBackdrop" id="deleteVtypeMargin" style="display: none;"> |
548 | <div class="customModalInner"> | 548 | <div class="customModalInner"> |
549 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> | 549 | <div class="customModelBody" style="border-radius: 5px 5px 0 0;"> |
550 | <table> | 550 | <table> |
551 | <tr> | 551 | <tr> |
552 | <td> | 552 | <td> |
553 | <img src="img/info.png" style="width: 50px;"> | 553 | <img src="img/info.png" style="width: 50px;"> |
554 | </td> | 554 | </td> |
555 | <td> | 555 | <td> |
556 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to delete this Margin Template ?</p> | 556 | <p style="padding: 5px 10px; margin-bottom: 0;">Are you sure that you want to delete this Margin Template ?</p> |
557 | </td> | 557 | </td> |
558 | </tr> | 558 | </tr> |
559 | </table> | 559 | </table> |
560 | </div> | 560 | </div> |
561 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> | 561 | <div class="customModelFooter text-right" style="border-radius: 0 0 5px 5px;"> |
562 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="confirmDeletVtypeMargin()">Yes</button> | 562 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="confirmDeletVtypeMargin()">Yes</button> |
563 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelVtypeMarginDelete()">Cancel</button> | 563 | <button class="btn" style="padding: 4px 0; width: 80px;" ng-click="cancelVtypeMarginDelete()">Cancel</button> |
564 | </div> | 564 | </div> |
565 | </div> | 565 | </div> |
566 | </div> | 566 | </div> |
567 | 567 | ||
568 | <!-- Le javascript | 568 | <!-- Le javascript |
569 | ================================================== --> | 569 | ================================================== --> |
570 | <!-- Placed at the end of the document so the pages load faster --> | 570 | <!-- Placed at the end of the document so the pages load faster --> |
571 | <!-- <script> | 571 | <!-- <script> |
572 | CKEDITOR.replace( 'editor2', { | 572 | CKEDITOR.replace( 'editor2', { |
573 | height: 250, | 573 | height: 250, |
574 | extraPlugins: 'divarea' | 574 | extraPlugins: 'divarea' |
575 | } ); | 575 | } ); |
576 | </script> --> | 576 | </script> --> |
bower.json
1 | { | 1 | { |
2 | "name": "acufuel", | 2 | "name": "acufuel", |
3 | "description": "", | 3 | "description": "", |
4 | "main": "index.js", | 4 | "main": "index.js", |
5 | "authors": [ | 5 | "authors": [ |
6 | "Rishav <rsingla.rishu@gmail.com>" | 6 | "Rishav <rsingla.rishu@gmail.com>" |
7 | ], | 7 | ], |
8 | "license": "ISC", | 8 | "license": "ISC", |
9 | "homepage": "", | 9 | "homepage": "", |
10 | "ignore": [ | 10 | "ignore": [ |
11 | "**/.*", | 11 | "**/.*", |
12 | "node_modules", | 12 | "node_modules", |
13 | "bower_components", | 13 | "bower_components", |
14 | "test", | 14 | "test", |
15 | "tests" | 15 | "tests" |
16 | ], | 16 | ], |
17 | "dependencies": { | 17 | "dependencies": { |
18 | "angular": "^1.6.2", | 18 | "angular": "^1.6.2", |
19 | "angular-animate": "^1.6.2", | 19 | "angular-animate": "^1.6.2", |
20 | "angular-route": "^1.6.2", | 20 | "angular-route": "^1.6.2", |
21 | "jquery": "^3.1.1", | 21 | "jquery": "^3.1.1", |
22 | "animate.css": "^3.5.2", | 22 | "animate.css": "^3.5.2", |
23 | "bootstrap": "^3.3.7", | 23 | "bootstrap": "^3.3.7", |
24 | "font-awesome": "fontawesome#^4.7.0", | 24 | "font-awesome": "fontawesome#^4.7.0", |
25 | "angular-ui-router": "^0.4.2", | 25 | "angular-ui-router": "^0.4.2", |
26 | "bootstrap-toggle": "^2.2.2", | 26 | "bootstrap-toggle": "^2.2.2", |
27 | "fullcalendar": "^3.2.0", | 27 | "fullcalendar": "^3.2.0", |
28 | "toastr": "^2.1.3", | 28 | "toastr": "^2.1.3", |
29 | "angular-bootstrap": "^2.5.0", | 29 | "angular-bootstrap": "^2.5.0", |
30 | "jqGrid": "^5.2.0", | 30 | "jqGrid": "^5.2.0", |
31 | "Autocomplete": "autocomplete#^2.0.5", | 31 | "Autocomplete": "autocomplete#^2.0.5", |
32 | "DataTables": "~1.10.15", | 32 | "DataTables": "~1.10.15", |
33 | "angular-cookies": "~1.6.4", | 33 | "angular-cookies": "~1.6.4", |
34 | "angular-resource": "~1.6.4", | 34 | "angular-resource": "~1.6.4", |
35 | "angular-xeditable": "~0.7.1", | 35 | "angular-xeditable": "~0.7.1", |
36 | "angular-bootstrap-toggle": "~0.1.2", | 36 | "angular-bootstrap-toggle": "~0.1.2", |
37 | "angular-ui-select2": "^0.0.5", | 37 | "angular-ui-select2": "^0.0.5", |
38 | "angular-ckeditor": "^1.0.3", | 38 | "angular-ckeditor": "^1.0.3", |
39 | "angular-ui-calendar": "^1.0.2" | 39 | "angular-ui-calendar": "^1.0.2", |
40 | "angular-dragdrop": "^1.0.13" | ||
40 | } | 41 | } |
41 | } | 42 | } |
42 | 43 |