Commit 03cf7c38882c8b034e49719d3360dd5556ca8bc3

Authored by Swarn Singh
1 parent 22787c6693
Exists in master

minor changes due to ui updation and integrated schedular

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 }
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>
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, '&amp;')
710 .replace(/</g, '&lt;')
711 .replace(/>/g, '&gt;')
712 .replace(/'/g, '&#039;')
713 .replace(/"/g, '&quot;')
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 || '') || '&nbsp;') + // 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>&nbsp;</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"> &nbsp; 127 <input type="text" placeholder="max" style="width: 36px;" ng-model="tier.margin" ng-show="showEditTier"> &nbsp;
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 &nbsp; 154 &nbsp;
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">&nbsp;</div> 171 <!-- <div class="row">&nbsp;</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>&nbsp;&nbsp; 175 <button type="submit" class="btn btn-success"><i class="icon-ok"></i> Save Form</button>&nbsp;&nbsp;
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"> &nbsp; 299 <input type="text" placeholder="max" style="width: 36px;" ng-model="tier.margin" ng-show="showEditTier"> &nbsp;
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 &nbsp; 326 &nbsp;
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">&nbsp;</div> 342 <!-- <div class="row">&nbsp;</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>&nbsp;&nbsp; 346 <button type="submit" class="btn btn-success"><i class="icon-ok"></i> Save Form</button>&nbsp;&nbsp;
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> -->
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