├── README.md
├── css
├── bootstrap-responsive.css
├── bootstrap-responsive.min.css
├── bootstrap.css
└── bootstrap.min.css
├── img
├── glyphicons-halflings-white.png
└── glyphicons-halflings.png
├── index.html
├── lib
├── angular.js
├── bootstrap.js
├── bootstrap.min.js
├── d3.v2.js
├── hammer.js
├── jquery-1.8.0.js
├── sylvester.src.js
└── underscore.js
├── spreadsheet.html
└── src
└── core.coffee
/README.md:
--------------------------------------------------------------------------------
1 | spreadsheet
2 | ===========
3 |
4 | This is a git repo used to create blog post about building an Angular.js spreadsheet.
5 |
--------------------------------------------------------------------------------
/css/bootstrap-responsive.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.2.1
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */
10 |
11 | .clearfix {
12 | *zoom: 1;
13 | }
14 |
15 | .clearfix:before,
16 | .clearfix:after {
17 | display: table;
18 | line-height: 0;
19 | content: "";
20 | }
21 |
22 | .clearfix:after {
23 | clear: both;
24 | }
25 |
26 | .hide-text {
27 | font: 0/0 a;
28 | color: transparent;
29 | text-shadow: none;
30 | background-color: transparent;
31 | border: 0;
32 | }
33 |
34 | .input-block-level {
35 | display: block;
36 | width: 100%;
37 | min-height: 30px;
38 | -webkit-box-sizing: border-box;
39 | -moz-box-sizing: border-box;
40 | box-sizing: border-box;
41 | }
42 |
43 | .hidden {
44 | display: none;
45 | visibility: hidden;
46 | }
47 |
48 | .visible-phone {
49 | display: none !important;
50 | }
51 |
52 | .visible-tablet {
53 | display: none !important;
54 | }
55 |
56 | .hidden-desktop {
57 | display: none !important;
58 | }
59 |
60 | .visible-desktop {
61 | display: inherit !important;
62 | }
63 |
64 | @media (min-width: 768px) and (max-width: 979px) {
65 | .hidden-desktop {
66 | display: inherit !important;
67 | }
68 | .visible-desktop {
69 | display: none !important ;
70 | }
71 | .visible-tablet {
72 | display: inherit !important;
73 | }
74 | .hidden-tablet {
75 | display: none !important;
76 | }
77 | }
78 |
79 | @media (max-width: 767px) {
80 | .hidden-desktop {
81 | display: inherit !important;
82 | }
83 | .visible-desktop {
84 | display: none !important;
85 | }
86 | .visible-phone {
87 | display: inherit !important;
88 | }
89 | .hidden-phone {
90 | display: none !important;
91 | }
92 | }
93 |
94 | @media (min-width: 1200px) {
95 | .row {
96 | margin-left: -30px;
97 | *zoom: 1;
98 | }
99 | .row:before,
100 | .row:after {
101 | display: table;
102 | line-height: 0;
103 | content: "";
104 | }
105 | .row:after {
106 | clear: both;
107 | }
108 | [class*="span"] {
109 | float: left;
110 | min-height: 1px;
111 | margin-left: 30px;
112 | }
113 | .container,
114 | .navbar-static-top .container,
115 | .navbar-fixed-top .container,
116 | .navbar-fixed-bottom .container {
117 | width: 1170px;
118 | }
119 | .span12 {
120 | width: 1170px;
121 | }
122 | .span11 {
123 | width: 1070px;
124 | }
125 | .span10 {
126 | width: 970px;
127 | }
128 | .span9 {
129 | width: 870px;
130 | }
131 | .span8 {
132 | width: 770px;
133 | }
134 | .span7 {
135 | width: 670px;
136 | }
137 | .span6 {
138 | width: 570px;
139 | }
140 | .span5 {
141 | width: 470px;
142 | }
143 | .span4 {
144 | width: 370px;
145 | }
146 | .span3 {
147 | width: 270px;
148 | }
149 | .span2 {
150 | width: 170px;
151 | }
152 | .span1 {
153 | width: 70px;
154 | }
155 | .offset12 {
156 | margin-left: 1230px;
157 | }
158 | .offset11 {
159 | margin-left: 1130px;
160 | }
161 | .offset10 {
162 | margin-left: 1030px;
163 | }
164 | .offset9 {
165 | margin-left: 930px;
166 | }
167 | .offset8 {
168 | margin-left: 830px;
169 | }
170 | .offset7 {
171 | margin-left: 730px;
172 | }
173 | .offset6 {
174 | margin-left: 630px;
175 | }
176 | .offset5 {
177 | margin-left: 530px;
178 | }
179 | .offset4 {
180 | margin-left: 430px;
181 | }
182 | .offset3 {
183 | margin-left: 330px;
184 | }
185 | .offset2 {
186 | margin-left: 230px;
187 | }
188 | .offset1 {
189 | margin-left: 130px;
190 | }
191 | .row-fluid {
192 | width: 100%;
193 | *zoom: 1;
194 | }
195 | .row-fluid:before,
196 | .row-fluid:after {
197 | display: table;
198 | line-height: 0;
199 | content: "";
200 | }
201 | .row-fluid:after {
202 | clear: both;
203 | }
204 | .row-fluid [class*="span"] {
205 | display: block;
206 | float: left;
207 | width: 100%;
208 | min-height: 30px;
209 | margin-left: 2.564102564102564%;
210 | *margin-left: 2.5109110747408616%;
211 | -webkit-box-sizing: border-box;
212 | -moz-box-sizing: border-box;
213 | box-sizing: border-box;
214 | }
215 | .row-fluid [class*="span"]:first-child {
216 | margin-left: 0;
217 | }
218 | .row-fluid .controls-row [class*="span"] + [class*="span"] {
219 | margin-left: 2.564102564102564%;
220 | }
221 | .row-fluid .span12 {
222 | width: 100%;
223 | *width: 99.94680851063829%;
224 | }
225 | .row-fluid .span11 {
226 | width: 91.45299145299145%;
227 | *width: 91.39979996362975%;
228 | }
229 | .row-fluid .span10 {
230 | width: 82.90598290598291%;
231 | *width: 82.8527914166212%;
232 | }
233 | .row-fluid .span9 {
234 | width: 74.35897435897436%;
235 | *width: 74.30578286961266%;
236 | }
237 | .row-fluid .span8 {
238 | width: 65.81196581196582%;
239 | *width: 65.75877432260411%;
240 | }
241 | .row-fluid .span7 {
242 | width: 57.26495726495726%;
243 | *width: 57.21176577559556%;
244 | }
245 | .row-fluid .span6 {
246 | width: 48.717948717948715%;
247 | *width: 48.664757228587014%;
248 | }
249 | .row-fluid .span5 {
250 | width: 40.17094017094017%;
251 | *width: 40.11774868157847%;
252 | }
253 | .row-fluid .span4 {
254 | width: 31.623931623931625%;
255 | *width: 31.570740134569924%;
256 | }
257 | .row-fluid .span3 {
258 | width: 23.076923076923077%;
259 | *width: 23.023731587561375%;
260 | }
261 | .row-fluid .span2 {
262 | width: 14.52991452991453%;
263 | *width: 14.476723040552828%;
264 | }
265 | .row-fluid .span1 {
266 | width: 5.982905982905983%;
267 | *width: 5.929714493544281%;
268 | }
269 | .row-fluid .offset12 {
270 | margin-left: 105.12820512820512%;
271 | *margin-left: 105.02182214948171%;
272 | }
273 | .row-fluid .offset12:first-child {
274 | margin-left: 102.56410256410257%;
275 | *margin-left: 102.45771958537915%;
276 | }
277 | .row-fluid .offset11 {
278 | margin-left: 96.58119658119658%;
279 | *margin-left: 96.47481360247316%;
280 | }
281 | .row-fluid .offset11:first-child {
282 | margin-left: 94.01709401709402%;
283 | *margin-left: 93.91071103837061%;
284 | }
285 | .row-fluid .offset10 {
286 | margin-left: 88.03418803418803%;
287 | *margin-left: 87.92780505546462%;
288 | }
289 | .row-fluid .offset10:first-child {
290 | margin-left: 85.47008547008548%;
291 | *margin-left: 85.36370249136206%;
292 | }
293 | .row-fluid .offset9 {
294 | margin-left: 79.48717948717949%;
295 | *margin-left: 79.38079650845607%;
296 | }
297 | .row-fluid .offset9:first-child {
298 | margin-left: 76.92307692307693%;
299 | *margin-left: 76.81669394435352%;
300 | }
301 | .row-fluid .offset8 {
302 | margin-left: 70.94017094017094%;
303 | *margin-left: 70.83378796144753%;
304 | }
305 | .row-fluid .offset8:first-child {
306 | margin-left: 68.37606837606839%;
307 | *margin-left: 68.26968539734497%;
308 | }
309 | .row-fluid .offset7 {
310 | margin-left: 62.393162393162385%;
311 | *margin-left: 62.28677941443899%;
312 | }
313 | .row-fluid .offset7:first-child {
314 | margin-left: 59.82905982905982%;
315 | *margin-left: 59.72267685033642%;
316 | }
317 | .row-fluid .offset6 {
318 | margin-left: 53.84615384615384%;
319 | *margin-left: 53.739770867430444%;
320 | }
321 | .row-fluid .offset6:first-child {
322 | margin-left: 51.28205128205128%;
323 | *margin-left: 51.175668303327875%;
324 | }
325 | .row-fluid .offset5 {
326 | margin-left: 45.299145299145295%;
327 | *margin-left: 45.1927623204219%;
328 | }
329 | .row-fluid .offset5:first-child {
330 | margin-left: 42.73504273504273%;
331 | *margin-left: 42.62865975631933%;
332 | }
333 | .row-fluid .offset4 {
334 | margin-left: 36.75213675213675%;
335 | *margin-left: 36.645753773413354%;
336 | }
337 | .row-fluid .offset4:first-child {
338 | margin-left: 34.18803418803419%;
339 | *margin-left: 34.081651209310785%;
340 | }
341 | .row-fluid .offset3 {
342 | margin-left: 28.205128205128204%;
343 | *margin-left: 28.0987452264048%;
344 | }
345 | .row-fluid .offset3:first-child {
346 | margin-left: 25.641025641025642%;
347 | *margin-left: 25.53464266230224%;
348 | }
349 | .row-fluid .offset2 {
350 | margin-left: 19.65811965811966%;
351 | *margin-left: 19.551736679396257%;
352 | }
353 | .row-fluid .offset2:first-child {
354 | margin-left: 17.094017094017094%;
355 | *margin-left: 16.98763411529369%;
356 | }
357 | .row-fluid .offset1 {
358 | margin-left: 11.11111111111111%;
359 | *margin-left: 11.004728132387708%;
360 | }
361 | .row-fluid .offset1:first-child {
362 | margin-left: 8.547008547008547%;
363 | *margin-left: 8.440625568285142%;
364 | }
365 | input,
366 | textarea,
367 | .uneditable-input {
368 | margin-left: 0;
369 | }
370 | .controls-row [class*="span"] + [class*="span"] {
371 | margin-left: 30px;
372 | }
373 | input.span12,
374 | textarea.span12,
375 | .uneditable-input.span12 {
376 | width: 1156px;
377 | }
378 | input.span11,
379 | textarea.span11,
380 | .uneditable-input.span11 {
381 | width: 1056px;
382 | }
383 | input.span10,
384 | textarea.span10,
385 | .uneditable-input.span10 {
386 | width: 956px;
387 | }
388 | input.span9,
389 | textarea.span9,
390 | .uneditable-input.span9 {
391 | width: 856px;
392 | }
393 | input.span8,
394 | textarea.span8,
395 | .uneditable-input.span8 {
396 | width: 756px;
397 | }
398 | input.span7,
399 | textarea.span7,
400 | .uneditable-input.span7 {
401 | width: 656px;
402 | }
403 | input.span6,
404 | textarea.span6,
405 | .uneditable-input.span6 {
406 | width: 556px;
407 | }
408 | input.span5,
409 | textarea.span5,
410 | .uneditable-input.span5 {
411 | width: 456px;
412 | }
413 | input.span4,
414 | textarea.span4,
415 | .uneditable-input.span4 {
416 | width: 356px;
417 | }
418 | input.span3,
419 | textarea.span3,
420 | .uneditable-input.span3 {
421 | width: 256px;
422 | }
423 | input.span2,
424 | textarea.span2,
425 | .uneditable-input.span2 {
426 | width: 156px;
427 | }
428 | input.span1,
429 | textarea.span1,
430 | .uneditable-input.span1 {
431 | width: 56px;
432 | }
433 | .thumbnails {
434 | margin-left: -30px;
435 | }
436 | .thumbnails > li {
437 | margin-left: 30px;
438 | }
439 | .row-fluid .thumbnails {
440 | margin-left: 0;
441 | }
442 | }
443 |
444 | @media (min-width: 768px) and (max-width: 979px) {
445 | .row {
446 | margin-left: -20px;
447 | *zoom: 1;
448 | }
449 | .row:before,
450 | .row:after {
451 | display: table;
452 | line-height: 0;
453 | content: "";
454 | }
455 | .row:after {
456 | clear: both;
457 | }
458 | [class*="span"] {
459 | float: left;
460 | min-height: 1px;
461 | margin-left: 20px;
462 | }
463 | .container,
464 | .navbar-static-top .container,
465 | .navbar-fixed-top .container,
466 | .navbar-fixed-bottom .container {
467 | width: 724px;
468 | }
469 | .span12 {
470 | width: 724px;
471 | }
472 | .span11 {
473 | width: 662px;
474 | }
475 | .span10 {
476 | width: 600px;
477 | }
478 | .span9 {
479 | width: 538px;
480 | }
481 | .span8 {
482 | width: 476px;
483 | }
484 | .span7 {
485 | width: 414px;
486 | }
487 | .span6 {
488 | width: 352px;
489 | }
490 | .span5 {
491 | width: 290px;
492 | }
493 | .span4 {
494 | width: 228px;
495 | }
496 | .span3 {
497 | width: 166px;
498 | }
499 | .span2 {
500 | width: 104px;
501 | }
502 | .span1 {
503 | width: 42px;
504 | }
505 | .offset12 {
506 | margin-left: 764px;
507 | }
508 | .offset11 {
509 | margin-left: 702px;
510 | }
511 | .offset10 {
512 | margin-left: 640px;
513 | }
514 | .offset9 {
515 | margin-left: 578px;
516 | }
517 | .offset8 {
518 | margin-left: 516px;
519 | }
520 | .offset7 {
521 | margin-left: 454px;
522 | }
523 | .offset6 {
524 | margin-left: 392px;
525 | }
526 | .offset5 {
527 | margin-left: 330px;
528 | }
529 | .offset4 {
530 | margin-left: 268px;
531 | }
532 | .offset3 {
533 | margin-left: 206px;
534 | }
535 | .offset2 {
536 | margin-left: 144px;
537 | }
538 | .offset1 {
539 | margin-left: 82px;
540 | }
541 | .row-fluid {
542 | width: 100%;
543 | *zoom: 1;
544 | }
545 | .row-fluid:before,
546 | .row-fluid:after {
547 | display: table;
548 | line-height: 0;
549 | content: "";
550 | }
551 | .row-fluid:after {
552 | clear: both;
553 | }
554 | .row-fluid [class*="span"] {
555 | display: block;
556 | float: left;
557 | width: 100%;
558 | min-height: 30px;
559 | margin-left: 2.7624309392265194%;
560 | *margin-left: 2.709239449864817%;
561 | -webkit-box-sizing: border-box;
562 | -moz-box-sizing: border-box;
563 | box-sizing: border-box;
564 | }
565 | .row-fluid [class*="span"]:first-child {
566 | margin-left: 0;
567 | }
568 | .row-fluid .controls-row [class*="span"] + [class*="span"] {
569 | margin-left: 2.7624309392265194%;
570 | }
571 | .row-fluid .span12 {
572 | width: 100%;
573 | *width: 99.94680851063829%;
574 | }
575 | .row-fluid .span11 {
576 | width: 91.43646408839778%;
577 | *width: 91.38327259903608%;
578 | }
579 | .row-fluid .span10 {
580 | width: 82.87292817679558%;
581 | *width: 82.81973668743387%;
582 | }
583 | .row-fluid .span9 {
584 | width: 74.30939226519337%;
585 | *width: 74.25620077583166%;
586 | }
587 | .row-fluid .span8 {
588 | width: 65.74585635359117%;
589 | *width: 65.69266486422946%;
590 | }
591 | .row-fluid .span7 {
592 | width: 57.18232044198895%;
593 | *width: 57.12912895262725%;
594 | }
595 | .row-fluid .span6 {
596 | width: 48.61878453038674%;
597 | *width: 48.56559304102504%;
598 | }
599 | .row-fluid .span5 {
600 | width: 40.05524861878453%;
601 | *width: 40.00205712942283%;
602 | }
603 | .row-fluid .span4 {
604 | width: 31.491712707182323%;
605 | *width: 31.43852121782062%;
606 | }
607 | .row-fluid .span3 {
608 | width: 22.92817679558011%;
609 | *width: 22.87498530621841%;
610 | }
611 | .row-fluid .span2 {
612 | width: 14.3646408839779%;
613 | *width: 14.311449394616199%;
614 | }
615 | .row-fluid .span1 {
616 | width: 5.801104972375691%;
617 | *width: 5.747913483013988%;
618 | }
619 | .row-fluid .offset12 {
620 | margin-left: 105.52486187845304%;
621 | *margin-left: 105.41847889972962%;
622 | }
623 | .row-fluid .offset12:first-child {
624 | margin-left: 102.76243093922652%;
625 | *margin-left: 102.6560479605031%;
626 | }
627 | .row-fluid .offset11 {
628 | margin-left: 96.96132596685082%;
629 | *margin-left: 96.8549429881274%;
630 | }
631 | .row-fluid .offset11:first-child {
632 | margin-left: 94.1988950276243%;
633 | *margin-left: 94.09251204890089%;
634 | }
635 | .row-fluid .offset10 {
636 | margin-left: 88.39779005524862%;
637 | *margin-left: 88.2914070765252%;
638 | }
639 | .row-fluid .offset10:first-child {
640 | margin-left: 85.6353591160221%;
641 | *margin-left: 85.52897613729868%;
642 | }
643 | .row-fluid .offset9 {
644 | margin-left: 79.8342541436464%;
645 | *margin-left: 79.72787116492299%;
646 | }
647 | .row-fluid .offset9:first-child {
648 | margin-left: 77.07182320441989%;
649 | *margin-left: 76.96544022569647%;
650 | }
651 | .row-fluid .offset8 {
652 | margin-left: 71.2707182320442%;
653 | *margin-left: 71.16433525332079%;
654 | }
655 | .row-fluid .offset8:first-child {
656 | margin-left: 68.50828729281768%;
657 | *margin-left: 68.40190431409427%;
658 | }
659 | .row-fluid .offset7 {
660 | margin-left: 62.70718232044199%;
661 | *margin-left: 62.600799341718584%;
662 | }
663 | .row-fluid .offset7:first-child {
664 | margin-left: 59.94475138121547%;
665 | *margin-left: 59.838368402492065%;
666 | }
667 | .row-fluid .offset6 {
668 | margin-left: 54.14364640883978%;
669 | *margin-left: 54.037263430116376%;
670 | }
671 | .row-fluid .offset6:first-child {
672 | margin-left: 51.38121546961326%;
673 | *margin-left: 51.27483249088986%;
674 | }
675 | .row-fluid .offset5 {
676 | margin-left: 45.58011049723757%;
677 | *margin-left: 45.47372751851417%;
678 | }
679 | .row-fluid .offset5:first-child {
680 | margin-left: 42.81767955801105%;
681 | *margin-left: 42.71129657928765%;
682 | }
683 | .row-fluid .offset4 {
684 | margin-left: 37.01657458563536%;
685 | *margin-left: 36.91019160691196%;
686 | }
687 | .row-fluid .offset4:first-child {
688 | margin-left: 34.25414364640884%;
689 | *margin-left: 34.14776066768544%;
690 | }
691 | .row-fluid .offset3 {
692 | margin-left: 28.45303867403315%;
693 | *margin-left: 28.346655695309746%;
694 | }
695 | .row-fluid .offset3:first-child {
696 | margin-left: 25.69060773480663%;
697 | *margin-left: 25.584224756083227%;
698 | }
699 | .row-fluid .offset2 {
700 | margin-left: 19.88950276243094%;
701 | *margin-left: 19.783119783707537%;
702 | }
703 | .row-fluid .offset2:first-child {
704 | margin-left: 17.12707182320442%;
705 | *margin-left: 17.02068884448102%;
706 | }
707 | .row-fluid .offset1 {
708 | margin-left: 11.32596685082873%;
709 | *margin-left: 11.219583872105325%;
710 | }
711 | .row-fluid .offset1:first-child {
712 | margin-left: 8.56353591160221%;
713 | *margin-left: 8.457152932878806%;
714 | }
715 | input,
716 | textarea,
717 | .uneditable-input {
718 | margin-left: 0;
719 | }
720 | .controls-row [class*="span"] + [class*="span"] {
721 | margin-left: 20px;
722 | }
723 | input.span12,
724 | textarea.span12,
725 | .uneditable-input.span12 {
726 | width: 710px;
727 | }
728 | input.span11,
729 | textarea.span11,
730 | .uneditable-input.span11 {
731 | width: 648px;
732 | }
733 | input.span10,
734 | textarea.span10,
735 | .uneditable-input.span10 {
736 | width: 586px;
737 | }
738 | input.span9,
739 | textarea.span9,
740 | .uneditable-input.span9 {
741 | width: 524px;
742 | }
743 | input.span8,
744 | textarea.span8,
745 | .uneditable-input.span8 {
746 | width: 462px;
747 | }
748 | input.span7,
749 | textarea.span7,
750 | .uneditable-input.span7 {
751 | width: 400px;
752 | }
753 | input.span6,
754 | textarea.span6,
755 | .uneditable-input.span6 {
756 | width: 338px;
757 | }
758 | input.span5,
759 | textarea.span5,
760 | .uneditable-input.span5 {
761 | width: 276px;
762 | }
763 | input.span4,
764 | textarea.span4,
765 | .uneditable-input.span4 {
766 | width: 214px;
767 | }
768 | input.span3,
769 | textarea.span3,
770 | .uneditable-input.span3 {
771 | width: 152px;
772 | }
773 | input.span2,
774 | textarea.span2,
775 | .uneditable-input.span2 {
776 | width: 90px;
777 | }
778 | input.span1,
779 | textarea.span1,
780 | .uneditable-input.span1 {
781 | width: 28px;
782 | }
783 | }
784 |
785 | @media (max-width: 767px) {
786 | body {
787 | padding-right: 20px;
788 | padding-left: 20px;
789 | }
790 | .navbar-fixed-top,
791 | .navbar-fixed-bottom,
792 | .navbar-static-top {
793 | margin-right: -20px;
794 | margin-left: -20px;
795 | }
796 | .container-fluid {
797 | padding: 0;
798 | }
799 | .dl-horizontal dt {
800 | float: none;
801 | width: auto;
802 | clear: none;
803 | text-align: left;
804 | }
805 | .dl-horizontal dd {
806 | margin-left: 0;
807 | }
808 | .container {
809 | width: auto;
810 | }
811 | .row-fluid {
812 | width: 100%;
813 | }
814 | .row,
815 | .thumbnails {
816 | margin-left: 0;
817 | }
818 | .thumbnails > li {
819 | float: none;
820 | margin-left: 0;
821 | }
822 | [class*="span"],
823 | .uneditable-input[class*="span"],
824 | .row-fluid [class*="span"] {
825 | display: block;
826 | float: none;
827 | width: 100%;
828 | margin-left: 0;
829 | -webkit-box-sizing: border-box;
830 | -moz-box-sizing: border-box;
831 | box-sizing: border-box;
832 | }
833 | .span12,
834 | .row-fluid .span12 {
835 | width: 100%;
836 | -webkit-box-sizing: border-box;
837 | -moz-box-sizing: border-box;
838 | box-sizing: border-box;
839 | }
840 | .row-fluid [class*="offset"]:first-child {
841 | margin-left: 0;
842 | }
843 | .input-large,
844 | .input-xlarge,
845 | .input-xxlarge,
846 | input[class*="span"],
847 | select[class*="span"],
848 | textarea[class*="span"],
849 | .uneditable-input {
850 | display: block;
851 | width: 100%;
852 | min-height: 30px;
853 | -webkit-box-sizing: border-box;
854 | -moz-box-sizing: border-box;
855 | box-sizing: border-box;
856 | }
857 | .input-prepend input,
858 | .input-append input,
859 | .input-prepend input[class*="span"],
860 | .input-append input[class*="span"] {
861 | display: inline-block;
862 | width: auto;
863 | }
864 | .controls-row [class*="span"] + [class*="span"] {
865 | margin-left: 0;
866 | }
867 | .modal {
868 | position: fixed;
869 | top: 20px;
870 | right: 20px;
871 | left: 20px;
872 | width: auto;
873 | margin: 0;
874 | }
875 | .modal.fade {
876 | top: -100px;
877 | }
878 | .modal.fade.in {
879 | top: 20px;
880 | }
881 | }
882 |
883 | @media (max-width: 480px) {
884 | .nav-collapse {
885 | -webkit-transform: translate3d(0, 0, 0);
886 | }
887 | .page-header h1 small {
888 | display: block;
889 | line-height: 20px;
890 | }
891 | input[type="checkbox"],
892 | input[type="radio"] {
893 | border: 1px solid #ccc;
894 | }
895 | .form-horizontal .control-label {
896 | float: none;
897 | width: auto;
898 | padding-top: 0;
899 | text-align: left;
900 | }
901 | .form-horizontal .controls {
902 | margin-left: 0;
903 | }
904 | .form-horizontal .control-list {
905 | padding-top: 0;
906 | }
907 | .form-horizontal .form-actions {
908 | padding-right: 10px;
909 | padding-left: 10px;
910 | }
911 | .media .pull-left,
912 | .media .pull-right {
913 | display: block;
914 | float: none;
915 | margin-bottom: 10px;
916 | }
917 | .media-object {
918 | margin-right: 0;
919 | margin-left: 0;
920 | }
921 | .modal {
922 | top: 10px;
923 | right: 10px;
924 | left: 10px;
925 | }
926 | .modal-header .close {
927 | padding: 10px;
928 | margin: -10px;
929 | }
930 | .carousel-caption {
931 | position: static;
932 | }
933 | }
934 |
935 | @media (max-width: 979px) {
936 | body {
937 | padding-top: 0;
938 | }
939 | .navbar-fixed-top,
940 | .navbar-fixed-bottom {
941 | position: static;
942 | }
943 | .navbar-fixed-top {
944 | margin-bottom: 20px;
945 | }
946 | .navbar-fixed-bottom {
947 | margin-top: 20px;
948 | }
949 | .navbar-fixed-top .navbar-inner,
950 | .navbar-fixed-bottom .navbar-inner {
951 | padding: 5px;
952 | }
953 | .navbar .container {
954 | width: auto;
955 | padding: 0;
956 | }
957 | .navbar .brand {
958 | padding-right: 10px;
959 | padding-left: 10px;
960 | margin: 0 0 0 -5px;
961 | }
962 | .nav-collapse {
963 | clear: both;
964 | }
965 | .nav-collapse .nav {
966 | float: none;
967 | margin: 0 0 10px;
968 | }
969 | .nav-collapse .nav > li {
970 | float: none;
971 | }
972 | .nav-collapse .nav > li > a {
973 | margin-bottom: 2px;
974 | }
975 | .nav-collapse .nav > .divider-vertical {
976 | display: none;
977 | }
978 | .nav-collapse .nav .nav-header {
979 | color: #777777;
980 | text-shadow: none;
981 | }
982 | .nav-collapse .nav > li > a,
983 | .nav-collapse .dropdown-menu a {
984 | padding: 9px 15px;
985 | font-weight: bold;
986 | color: #777777;
987 | -webkit-border-radius: 3px;
988 | -moz-border-radius: 3px;
989 | border-radius: 3px;
990 | }
991 | .nav-collapse .btn {
992 | padding: 4px 10px 4px;
993 | font-weight: normal;
994 | -webkit-border-radius: 4px;
995 | -moz-border-radius: 4px;
996 | border-radius: 4px;
997 | }
998 | .nav-collapse .dropdown-menu li + li a {
999 | margin-bottom: 2px;
1000 | }
1001 | .nav-collapse .nav > li > a:hover,
1002 | .nav-collapse .dropdown-menu a:hover {
1003 | background-color: #f2f2f2;
1004 | }
1005 | .navbar-inverse .nav-collapse .nav > li > a,
1006 | .navbar-inverse .nav-collapse .dropdown-menu a {
1007 | color: #999999;
1008 | }
1009 | .navbar-inverse .nav-collapse .nav > li > a:hover,
1010 | .navbar-inverse .nav-collapse .dropdown-menu a:hover {
1011 | background-color: #111111;
1012 | }
1013 | .nav-collapse.in .btn-group {
1014 | padding: 0;
1015 | margin-top: 5px;
1016 | }
1017 | .nav-collapse .dropdown-menu {
1018 | position: static;
1019 | top: auto;
1020 | left: auto;
1021 | display: none;
1022 | float: none;
1023 | max-width: none;
1024 | padding: 0;
1025 | margin: 0 15px;
1026 | background-color: transparent;
1027 | border: none;
1028 | -webkit-border-radius: 0;
1029 | -moz-border-radius: 0;
1030 | border-radius: 0;
1031 | -webkit-box-shadow: none;
1032 | -moz-box-shadow: none;
1033 | box-shadow: none;
1034 | }
1035 | .nav-collapse .open > .dropdown-menu {
1036 | display: block;
1037 | }
1038 | .nav-collapse .dropdown-menu:before,
1039 | .nav-collapse .dropdown-menu:after {
1040 | display: none;
1041 | }
1042 | .nav-collapse .dropdown-menu .divider {
1043 | display: none;
1044 | }
1045 | .nav-collapse .nav > li > .dropdown-menu:before,
1046 | .nav-collapse .nav > li > .dropdown-menu:after {
1047 | display: none;
1048 | }
1049 | .nav-collapse .navbar-form,
1050 | .nav-collapse .navbar-search {
1051 | float: none;
1052 | padding: 10px 15px;
1053 | margin: 10px 0;
1054 | border-top: 1px solid #f2f2f2;
1055 | border-bottom: 1px solid #f2f2f2;
1056 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
1057 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
1058 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
1059 | }
1060 | .navbar-inverse .nav-collapse .navbar-form,
1061 | .navbar-inverse .nav-collapse .navbar-search {
1062 | border-top-color: #111111;
1063 | border-bottom-color: #111111;
1064 | }
1065 | .navbar .nav-collapse .nav.pull-right {
1066 | float: none;
1067 | margin-left: 0;
1068 | }
1069 | .nav-collapse,
1070 | .nav-collapse.collapse {
1071 | height: 0;
1072 | overflow: hidden;
1073 | }
1074 | .navbar .btn-navbar {
1075 | display: block;
1076 | }
1077 | .navbar-static .navbar-inner {
1078 | padding-right: 10px;
1079 | padding-left: 10px;
1080 | }
1081 | }
1082 |
1083 | @media (min-width: 980px) {
1084 | .nav-collapse.collapse {
1085 | height: auto !important;
1086 | overflow: visible !important;
1087 | }
1088 | }
1089 |
--------------------------------------------------------------------------------
/css/bootstrap-responsive.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.2.1
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
10 |
--------------------------------------------------------------------------------
/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/graunked/spreadsheet/f8f1fbcbd9e74d02befae079f23c89b56b955c66/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/graunked/spreadsheet/f8f1fbcbd9e74d02befae079f23c89b56b955c66/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/lib/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap.js by @fat & @mdo
3 | * Copyright 2012 Twitter, Inc.
4 | * http://www.apache.org/licenses/LICENSE-2.0.txt
5 | */
6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()},e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")},e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=n,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},to:function(t){var n=this.$element.find(".item.active"),r=n.parent().children(),i=r.index(n),s=this;if(t>r.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){s.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",e(r[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0]});if(i.hasClass("active"))return;if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}},e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e(document).on("click.carousel.data-api","[data-slide]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data());i.carousel(s),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning)return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning)return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=typeof n=="object"&&n;i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;return n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=e(n),r.length||(r=t.parent()),r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||(s.toggleClass("open"),n.focus()),!1},keydown:function(t){var n,r,s,o,u,a;if(!/(38|40|27)/.test(t.keyCode))return;n=e(this),t.preventDefault(),t.stopPropagation();if(n.is(".disabled, :disabled"))return;o=i(n),u=o.hasClass("open");if(!u||u&&t.keyCode==27)return n.click();r=e("[role=menu] li:not(.divider) a",o);if(!r.length)return;a=r.index(r.filter(":focus")),t.keyCode==38&&a>0&&a--,t.keyCode==40&&a').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,e.proxy(this.removeBackdrop,this)):this.removeBackdrop()):t&&t()}},e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,this.options.trigger=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):this.options.trigger!="manual"&&(i=this.options.trigger=="hover"?"mouseenter":"focus",s=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this))),this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,t,this.$element.data()),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var e,t,n,r,i,s,o;if(this.hasContent()&&this.enabled){e=this.tip(),this.setContent(),this.options.animation&&e.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,t=/in/.test(s),e.detach().css({top:0,left:0,display:"block"}).insertAfter(this.$element),n=this.getPosition(t),r=e[0].offsetWidth,i=e[0].offsetHeight;switch(t?s.split(" ")[1]:s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}e.offset(o).addClass(s).addClass("in")}},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function r(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip();return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?r():n.detach(),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(t){return e.extend({},t?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);n[n.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}},e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover",title:"",delay:0,html:!1}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content > *")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-content")||(typeof n.content=="function"?n.content.call(t[0]):n.content),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}}),e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:''})}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var t=e(this),n=t.data("target")||t.attr("href"),r=/^#\w/.test(n)&&e(n);return r&&r.length&&[[r.position().top,n]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}},e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}},e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=e(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:t.top+t.height,left:t.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=!~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},blur:function(e){var t=this;setTimeout(function(){t.hide()},150)},click:function(e){e.stopPropagation(),e.preventDefault(),this.select()},mouseenter:function(t){this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")}},e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'',minLength:1},e.fn.typeahead.Constructor=t,e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;t.preventDefault(),n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))},e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
--------------------------------------------------------------------------------
/lib/hammer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Hammer.JS
3 | * version 0.6.1
4 | * author: Eight Media
5 | * https://github.com/EightMedia/hammer.js
6 | * Licensed under the MIT license.
7 | */
8 | function Hammer(element, options, undefined)
9 | {
10 | var self = this;
11 |
12 | var defaults = {
13 | // prevent the default event or not... might be buggy when false
14 | prevent_default : false,
15 | css_hacks : true,
16 |
17 | swipe : true,
18 | swipe_time : 200, // ms
19 | swipe_min_distance : 20, // pixels
20 |
21 | drag : true,
22 | drag_vertical : true,
23 | drag_horizontal : true,
24 | // minimum distance before the drag event starts
25 | drag_min_distance : 20, // pixels
26 |
27 | // pinch zoom and rotation
28 | transform : true,
29 | scale_treshold : 0.1,
30 | rotation_treshold : 15, // degrees
31 |
32 | tap : true,
33 | tap_double : true,
34 | tap_max_interval : 300,
35 | tap_max_distance : 10,
36 | tap_double_distance: 20,
37 |
38 | hold : true,
39 | hold_timeout : 500
40 | };
41 | options = mergeObject(defaults, options);
42 |
43 | // some css hacks
44 | (function() {
45 | if(!options.css_hacks) {
46 | return false;
47 | }
48 |
49 | var vendors = ['webkit','moz','ms','o',''];
50 | var css_props = {
51 | "userSelect": "none",
52 | "touchCallout": "none",
53 | "userDrag": "none",
54 | "tapHighlightColor": "rgba(0,0,0,0)"
55 | };
56 |
57 | var prop = '';
58 | for(var i = 0; i < vendors.length; i++) {
59 | for(var p in css_props) {
60 | prop = p;
61 | if(vendors[i]) {
62 | prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1);
63 | }
64 | element.style[ prop ] = css_props[p];
65 | }
66 | }
67 | })();
68 |
69 | // holds the distance that has been moved
70 | var _distance = 0;
71 |
72 | // holds the exact angle that has been moved
73 | var _angle = 0;
74 |
75 | // holds the diraction that has been moved
76 | var _direction = 0;
77 |
78 | // holds position movement for sliding
79 | var _pos = { };
80 |
81 | // how many fingers are on the screen
82 | var _fingers = 0;
83 |
84 | var _first = false;
85 |
86 | var _gesture = null;
87 | var _prev_gesture = null;
88 |
89 | var _touch_start_time = null;
90 | var _prev_tap_pos = {x: 0, y: 0};
91 | var _prev_tap_end_time = null;
92 |
93 | var _hold_timer = null;
94 |
95 | var _offset = {};
96 |
97 | // keep track of the mouse status
98 | var _mousedown = false;
99 |
100 | var _event_start;
101 | var _event_move;
102 | var _event_end;
103 |
104 | var _has_touch = ('ontouchstart' in window);
105 |
106 |
107 | /**
108 | * option setter/getter
109 | * @param string key
110 | * @param mixed value
111 | * @return mixed value
112 | */
113 | this.option = function(key, val) {
114 | if(val != undefined) {
115 | options[key] = val;
116 | }
117 |
118 | return options[key];
119 | };
120 |
121 |
122 | /**
123 | * angle to direction define
124 | * @param float angle
125 | * @return string direction
126 | */
127 | this.getDirectionFromAngle = function( angle ) {
128 | var directions = {
129 | down: angle >= 45 && angle < 135, //90
130 | left: angle >= 135 || angle <= -135, //180
131 | up: angle < -45 && angle > -135, //270
132 | right: angle >= -45 && angle <= 45 //0
133 | };
134 |
135 | var direction, key;
136 | for(key in directions){
137 | if(directions[key]){
138 | direction = key;
139 | break;
140 | }
141 | }
142 | return direction;
143 | };
144 |
145 |
146 | /**
147 | * destory events
148 | * @return void
149 | */
150 | this.destroy = function() {
151 | if(_has_touch) {
152 | removeEvent(element, "touchstart touchmove touchend touchcancel", handleEvents);
153 | }
154 | // for non-touch
155 | else {
156 | removeEvent(element, "mouseup mousedown mousemove", handleEvents);
157 | removeEvent(element, "mouseout", handleMouseOut);
158 | }
159 | };
160 |
161 |
162 | /**
163 | * count the number of fingers in the event
164 | * when no fingers are detected, one finger is returned (mouse pointer)
165 | * @param event
166 | * @return int fingers
167 | */
168 | function countFingers( event )
169 | {
170 | // there is a bug on android (until v4?) that touches is always 1,
171 | // so no multitouch is supported, e.g. no, zoom and rotation...
172 | return event.touches ? event.touches.length : 1;
173 | }
174 |
175 |
176 | /**
177 | * get the x and y positions from the event object
178 | * @param event
179 | * @return array [{ x: int, y: int }]
180 | */
181 | function getXYfromEvent( event )
182 | {
183 | event = event || window.event;
184 |
185 | // no touches, use the event pageX and pageY
186 | if(!_has_touch) {
187 | var doc = document,
188 | body = doc.body;
189 |
190 | return [{
191 | x: event.pageX || event.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && doc.clientLeft || 0 ),
192 | y: event.pageY || event.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && doc.clientTop || 0 )
193 | }];
194 | }
195 | // multitouch, return array with positions
196 | else {
197 | var pos = [], src;
198 | for(var t=0, len=event.touches.length; t touch_time) && (_distance > options.swipe_min_distance)) {
360 | // calculate the angle
361 | _angle = getAngle(_pos.start[0], _pos.move[0]);
362 | _direction = self.getDirectionFromAngle(_angle);
363 |
364 | _gesture = 'swipe';
365 |
366 | var position = { x: _pos.move[0].x - _offset.left,
367 | y: _pos.move[0].y - _offset.top };
368 |
369 | var event_obj = {
370 | originalEvent : event,
371 | position : position,
372 | direction : _direction,
373 | distance : _distance,
374 | distanceX : _distance_x,
375 | distanceY : _distance_y,
376 | angle : _angle
377 | };
378 |
379 | // normal slide event
380 | triggerEvent("swipe", event_obj);
381 | }
382 | },
383 |
384 |
385 | // drag gesture
386 | // fired on mousemove
387 | drag : function(event)
388 | {
389 | // get the distance we moved
390 | var _distance_x = _pos.move[0].x - _pos.start[0].x;
391 | var _distance_y = _pos.move[0].y - _pos.start[0].y;
392 | _distance = Math.sqrt(_distance_x * _distance_x + _distance_y * _distance_y);
393 |
394 | // drag
395 | // minimal movement required
396 | if(options.drag && (_distance > options.drag_min_distance) || _gesture == 'drag') {
397 | // calculate the angle
398 | _angle = getAngle(_pos.start[0], _pos.move[0]);
399 | _direction = self.getDirectionFromAngle(_angle);
400 |
401 | // check the movement and stop if we go in the wrong direction
402 | var is_vertical = (_direction == 'up' || _direction == 'down');
403 | if(((is_vertical && !options.drag_vertical) || (!is_vertical && !options.drag_horizontal))
404 | && (_distance > options.drag_min_distance)) {
405 | return;
406 | }
407 |
408 | _gesture = 'drag';
409 |
410 | var position = { x: _pos.move[0].x - _offset.left,
411 | y: _pos.move[0].y - _offset.top };
412 |
413 | var event_obj = {
414 | originalEvent : event,
415 | position : position,
416 | direction : _direction,
417 | distance : _distance,
418 | distanceX : _distance_x,
419 | distanceY : _distance_y,
420 | angle : _angle
421 | };
422 |
423 | // on the first time trigger the start event
424 | if(_first) {
425 | triggerEvent("dragstart", event_obj);
426 |
427 | _first = false;
428 | }
429 |
430 | // normal slide event
431 | triggerEvent("drag", event_obj);
432 |
433 | cancelEvent(event);
434 | }
435 | },
436 |
437 |
438 | // transform gesture
439 | // fired on touchmove
440 | transform : function(event)
441 | {
442 | if(options.transform) {
443 | if(countFingers(event) != 2) {
444 | return false;
445 | }
446 |
447 | var rotation = calculateRotation(_pos.start, _pos.move);
448 | var scale = calculateScale(_pos.start, _pos.move);
449 |
450 | if(_gesture != 'drag' &&
451 | (_gesture == 'transform' || Math.abs(1-scale) > options.scale_treshold || Math.abs(rotation) > options.rotation_treshold)) {
452 | _gesture = 'transform';
453 |
454 | _pos.center = { x: ((_pos.move[0].x + _pos.move[1].x) / 2) - _offset.left,
455 | y: ((_pos.move[0].y + _pos.move[1].y) / 2) - _offset.top };
456 |
457 | var event_obj = {
458 | originalEvent : event,
459 | position : _pos.center,
460 | scale : scale,
461 | rotation : rotation
462 | };
463 |
464 | // on the first time trigger the start event
465 | if(_first) {
466 | triggerEvent("transformstart", event_obj);
467 | _first = false;
468 | }
469 |
470 | triggerEvent("transform", event_obj);
471 |
472 | cancelEvent(event);
473 |
474 | return true;
475 | }
476 | }
477 |
478 | return false;
479 | },
480 |
481 |
482 | // tap and double tap gesture
483 | // fired on touchend
484 | tap : function(event)
485 | {
486 | // compare the kind of gesture by time
487 | var now = new Date().getTime();
488 | var touch_time = now - _touch_start_time;
489 |
490 | // dont fire when hold is fired
491 | if(options.hold && !(options.hold && options.hold_timeout > touch_time)) {
492 | return;
493 | }
494 |
495 | // when previous event was tap and the tap was max_interval ms ago
496 | var is_double_tap = (function(){
497 | if (_prev_tap_pos &&
498 | options.tap_double &&
499 | _prev_gesture == 'tap' &&
500 | (_touch_start_time - _prev_tap_end_time) < options.tap_max_interval)
501 | {
502 | var x_distance = Math.abs(_prev_tap_pos[0].x - _pos.start[0].x);
503 | var y_distance = Math.abs(_prev_tap_pos[0].y - _pos.start[0].y);
504 | return (_prev_tap_pos && _pos.start && Math.max(x_distance, y_distance) < options.tap_double_distance);
505 | }
506 | return false;
507 | })();
508 |
509 | if(is_double_tap) {
510 | _gesture = 'double_tap';
511 | _prev_tap_end_time = null;
512 |
513 | triggerEvent("doubletap", {
514 | originalEvent : event,
515 | position : _pos.start
516 | });
517 | cancelEvent(event);
518 | }
519 |
520 | // single tap is single touch
521 | else {
522 | var x_distance = (_pos.move) ? Math.abs(_pos.move[0].x - _pos.start[0].x) : 0;
523 | var y_distance = (_pos.move) ? Math.abs(_pos.move[0].y - _pos.start[0].y) : 0;
524 | _distance = Math.max(x_distance, y_distance);
525 |
526 | if(_distance < options.tap_max_distance) {
527 | _gesture = 'tap';
528 | _prev_tap_end_time = now;
529 | _prev_tap_pos = _pos.start;
530 |
531 | if(options.tap) {
532 | triggerEvent("tap", {
533 | originalEvent : event,
534 | position : _pos.start
535 | });
536 | cancelEvent(event);
537 | }
538 | }
539 | }
540 |
541 | }
542 |
543 | };
544 |
545 |
546 | function handleEvents(event)
547 | {
548 | switch(event.type)
549 | {
550 | case 'mousedown':
551 | case 'touchstart':
552 | _pos.start = getXYfromEvent(event);
553 | _touch_start_time = new Date().getTime();
554 | _fingers = countFingers(event);
555 | _first = true;
556 | _event_start = event;
557 |
558 | // borrowed from jquery offset https://github.com/jquery/jquery/blob/master/src/offset.js
559 | var box = element.getBoundingClientRect();
560 | var clientTop = element.clientTop || document.body.clientTop || 0;
561 | var clientLeft = element.clientLeft || document.body.clientLeft || 0;
562 | var scrollTop = window.pageYOffset || element.scrollTop || document.body.scrollTop;
563 | var scrollLeft = window.pageXOffset || element.scrollLeft || document.body.scrollLeft;
564 |
565 | _offset = {
566 | top: box.top + scrollTop - clientTop,
567 | left: box.left + scrollLeft - clientLeft
568 | };
569 |
570 | _mousedown = true;
571 |
572 | // hold gesture
573 | gestures.hold(event);
574 |
575 | if(options.prevent_default) {
576 | cancelEvent(event);
577 | }
578 | break;
579 |
580 | case 'mousemove':
581 | case 'touchmove':
582 | if(!_mousedown) {
583 | return false;
584 | }
585 | _event_move = event;
586 | _pos.move = getXYfromEvent(event);
587 |
588 | if(!gestures.transform(event)) {
589 | gestures.drag(event);
590 | }
591 | break;
592 |
593 | case 'mouseup':
594 | case 'mouseout':
595 | case 'touchcancel':
596 | case 'touchend':
597 | if(!_mousedown || (_gesture != 'transform' && event.touches && event.touches.length > 0)) {
598 | return false;
599 | }
600 |
601 | _mousedown = false;
602 | _event_end = event;
603 |
604 |
605 | // swipe gesture
606 | gestures.swipe(event);
607 |
608 |
609 | // drag gesture
610 | // dragstart is triggered, so dragend is possible
611 | if(_gesture == 'drag') {
612 | triggerEvent("dragend", {
613 | originalEvent : event,
614 | direction : _direction,
615 | distance : _distance,
616 | angle : _angle
617 | });
618 | }
619 |
620 | // transform
621 | // transformstart is triggered, so transformed is possible
622 | else if(_gesture == 'transform') {
623 | triggerEvent("transformend", {
624 | originalEvent : event,
625 | position : _pos.center,
626 | scale : calculateScale(_pos.start, _pos.move),
627 | rotation : calculateRotation(_pos.start, _pos.move)
628 | });
629 | }
630 | else {
631 | gestures.tap(_event_start);
632 | }
633 |
634 | _prev_gesture = _gesture;
635 |
636 | // trigger release event
637 | triggerEvent("release", {
638 | originalEvent : event,
639 | gesture : _gesture
640 | });
641 |
642 | // reset vars
643 | reset();
644 | break;
645 | }
646 | }
647 |
648 |
649 | function handleMouseOut(event) {
650 | if(!isInsideHammer(element, event.relatedTarget)) {
651 | handleEvents(event);
652 | }
653 | }
654 |
655 |
656 | // bind events for touch devices
657 | // except for windows phone 7.5, it doesnt support touch events..!
658 | if(_has_touch) {
659 | addEvent(element, "touchstart touchmove touchend touchcancel", handleEvents);
660 | }
661 | // for non-touch
662 | else {
663 | addEvent(element, "mouseup mousedown mousemove", handleEvents);
664 | addEvent(element, "mouseout", handleMouseOut);
665 | }
666 |
667 |
668 | /**
669 | * find if element is (inside) given parent element
670 | * @param object element
671 | * @param object parent
672 | * @return bool inside
673 | */
674 | function isInsideHammer(parent, child) {
675 | // get related target for IE
676 | if(!child && window.event && window.event.toElement){
677 | child = window.event.toElement;
678 | }
679 |
680 | if(parent === child){
681 | return true;
682 | }
683 |
684 | // loop over parentNodes of child until we find hammer element
685 | if(child){
686 | var node = child.parentNode;
687 | while(node !== null){
688 | if(node === parent){
689 | return true;
690 | };
691 | node = node.parentNode;
692 | }
693 | }
694 | return false;
695 | }
696 |
697 |
698 | /**
699 | * merge 2 objects into a new object
700 | * @param object obj1
701 | * @param object obj2
702 | * @return object merged object
703 | */
704 | function mergeObject(obj1, obj2) {
705 | var output = {};
706 |
707 | if(!obj2) {
708 | return obj1;
709 | }
710 |
711 | for (var prop in obj1) {
712 | if (prop in obj2) {
713 | output[prop] = obj2[prop];
714 | } else {
715 | output[prop] = obj1[prop];
716 | }
717 | }
718 | return output;
719 | }
720 |
721 |
722 | /**
723 | * check if object is a function
724 | * @param object obj
725 | * @return bool is function
726 | */
727 | function isFunction( obj ){
728 | return Object.prototype.toString.call( obj ) == "[object Function]";
729 | }
730 |
731 |
732 | /**
733 | * attach event
734 | * @param node element
735 | * @param string types
736 | * @param object callback
737 | */
738 | function addEvent(element, types, callback) {
739 | types = types.split(" ");
740 | for(var t= 0,len=types.length; t this.elements.length) ? null : this.elements[i-1];
34 | },
35 |
36 | // Returns the number of elements the vector has
37 | dimensions: function() {
38 | return this.elements.length;
39 | },
40 |
41 | // Returns the modulus ('length') of the vector
42 | modulus: function() {
43 | return Math.sqrt(this.dot(this));
44 | },
45 |
46 | // Returns true iff the vector is equal to the argument
47 | eql: function(vector) {
48 | var n = this.elements.length;
49 | var V = vector.elements || vector;
50 | if (n != V.length) { return false; }
51 | do {
52 | if (Math.abs(this.elements[n-1] - V[n-1]) > Sylvester.precision) { return false; }
53 | } while (--n);
54 | return true;
55 | },
56 |
57 | // Returns a copy of the vector
58 | dup: function() {
59 | return Vector.create(this.elements);
60 | },
61 |
62 | // Maps the vector to another vector according to the given function
63 | map: function(fn) {
64 | var elements = [];
65 | this.each(function(x, i) {
66 | elements.push(fn(x, i));
67 | });
68 | return Vector.create(elements);
69 | },
70 |
71 | // Calls the iterator for each element of the vector in turn
72 | each: function(fn) {
73 | var n = this.elements.length, k = n, i;
74 | do { i = k - n;
75 | fn(this.elements[i], i+1);
76 | } while (--n);
77 | },
78 |
79 | // Returns a new vector created by normalizing the receiver
80 | toUnitVector: function() {
81 | var r = this.modulus();
82 | if (r === 0) { return this.dup(); }
83 | return this.map(function(x) { return x/r; });
84 | },
85 |
86 | // Returns the angle between the vector and the argument (also a vector)
87 | angleFrom: function(vector) {
88 | var V = vector.elements || vector;
89 | var n = this.elements.length, k = n, i;
90 | if (n != V.length) { return null; }
91 | var dot = 0, mod1 = 0, mod2 = 0;
92 | // Work things out in parallel to save time
93 | this.each(function(x, i) {
94 | dot += x * V[i-1];
95 | mod1 += x * x;
96 | mod2 += V[i-1] * V[i-1];
97 | });
98 | mod1 = Math.sqrt(mod1); mod2 = Math.sqrt(mod2);
99 | if (mod1*mod2 === 0) { return null; }
100 | var theta = dot / (mod1*mod2);
101 | if (theta < -1) { theta = -1; }
102 | if (theta > 1) { theta = 1; }
103 | return Math.acos(theta);
104 | },
105 |
106 | // Returns true iff the vector is parallel to the argument
107 | isParallelTo: function(vector) {
108 | var angle = this.angleFrom(vector);
109 | return (angle === null) ? null : (angle <= Sylvester.precision);
110 | },
111 |
112 | // Returns true iff the vector is antiparallel to the argument
113 | isAntiparallelTo: function(vector) {
114 | var angle = this.angleFrom(vector);
115 | return (angle === null) ? null : (Math.abs(angle - Math.PI) <= Sylvester.precision);
116 | },
117 |
118 | // Returns true iff the vector is perpendicular to the argument
119 | isPerpendicularTo: function(vector) {
120 | var dot = this.dot(vector);
121 | return (dot === null) ? null : (Math.abs(dot) <= Sylvester.precision);
122 | },
123 |
124 | // Returns the result of adding the argument to the vector
125 | add: function(vector) {
126 | var V = vector.elements || vector;
127 | if (this.elements.length != V.length) { return null; }
128 | return this.map(function(x, i) { return x + V[i-1]; });
129 | },
130 |
131 | // Returns the result of subtracting the argument from the vector
132 | subtract: function(vector) {
133 | var V = vector.elements || vector;
134 | if (this.elements.length != V.length) { return null; }
135 | return this.map(function(x, i) { return x - V[i-1]; });
136 | },
137 |
138 | // Returns the result of multiplying the elements of the vector by the argument
139 | multiply: function(k) {
140 | return this.map(function(x) { return x*k; });
141 | },
142 |
143 | x: function(k) { return this.multiply(k); },
144 |
145 | // Returns the scalar product of the vector with the argument
146 | // Both vectors must have equal dimensionality
147 | dot: function(vector) {
148 | var V = vector.elements || vector;
149 | var i, product = 0, n = this.elements.length;
150 | if (n != V.length) { return null; }
151 | do { product += this.elements[n-1] * V[n-1]; } while (--n);
152 | return product;
153 | },
154 |
155 | // Returns the vector product of the vector with the argument
156 | // Both vectors must have dimensionality 3
157 | cross: function(vector) {
158 | var B = vector.elements || vector;
159 | if (this.elements.length != 3 || B.length != 3) { return null; }
160 | var A = this.elements;
161 | return Vector.create([
162 | (A[1] * B[2]) - (A[2] * B[1]),
163 | (A[2] * B[0]) - (A[0] * B[2]),
164 | (A[0] * B[1]) - (A[1] * B[0])
165 | ]);
166 | },
167 |
168 | // Returns the (absolute) largest element of the vector
169 | max: function() {
170 | var m = 0, n = this.elements.length, k = n, i;
171 | do { i = k - n;
172 | if (Math.abs(this.elements[i]) > Math.abs(m)) { m = this.elements[i]; }
173 | } while (--n);
174 | return m;
175 | },
176 |
177 | // Returns the index of the first match found
178 | indexOf: function(x) {
179 | var index = null, n = this.elements.length, k = n, i;
180 | do { i = k - n;
181 | if (index === null && this.elements[i] == x) {
182 | index = i + 1;
183 | }
184 | } while (--n);
185 | return index;
186 | },
187 |
188 | // Returns a diagonal matrix with the vector's elements as its diagonal elements
189 | toDiagonalMatrix: function() {
190 | return Matrix.Diagonal(this.elements);
191 | },
192 |
193 | // Returns the result of rounding the elements of the vector
194 | round: function() {
195 | return this.map(function(x) { return Math.round(x); });
196 | },
197 |
198 | // Returns a copy of the vector with elements set to the given value if they
199 | // differ from it by less than Sylvester.precision
200 | snapTo: function(x) {
201 | return this.map(function(y) {
202 | return (Math.abs(y - x) <= Sylvester.precision) ? x : y;
203 | });
204 | },
205 |
206 | // Returns the vector's distance from the argument, when considered as a point in space
207 | distanceFrom: function(obj) {
208 | if (obj.anchor) { return obj.distanceFrom(this); }
209 | var V = obj.elements || obj;
210 | if (V.length != this.elements.length) { return null; }
211 | var sum = 0, part;
212 | this.each(function(x, i) {
213 | part = x - V[i-1];
214 | sum += part * part;
215 | });
216 | return Math.sqrt(sum);
217 | },
218 |
219 | // Returns true if the vector is point on the given line
220 | liesOn: function(line) {
221 | return line.contains(this);
222 | },
223 |
224 | // Return true iff the vector is a point in the given plane
225 | liesIn: function(plane) {
226 | return plane.contains(this);
227 | },
228 |
229 | // Rotates the vector about the given object. The object should be a
230 | // point if the vector is 2D, and a line if it is 3D. Be careful with line directions!
231 | rotate: function(t, obj) {
232 | var V, R, x, y, z;
233 | switch (this.elements.length) {
234 | case 2:
235 | V = obj.elements || obj;
236 | if (V.length != 2) { return null; }
237 | R = Matrix.Rotation(t).elements;
238 | x = this.elements[0] - V[0];
239 | y = this.elements[1] - V[1];
240 | return Vector.create([
241 | V[0] + R[0][0] * x + R[0][1] * y,
242 | V[1] + R[1][0] * x + R[1][1] * y
243 | ]);
244 | break;
245 | case 3:
246 | if (!obj.direction) { return null; }
247 | var C = obj.pointClosestTo(this).elements;
248 | R = Matrix.Rotation(t, obj.direction).elements;
249 | x = this.elements[0] - C[0];
250 | y = this.elements[1] - C[1];
251 | z = this.elements[2] - C[2];
252 | return Vector.create([
253 | C[0] + R[0][0] * x + R[0][1] * y + R[0][2] * z,
254 | C[1] + R[1][0] * x + R[1][1] * y + R[1][2] * z,
255 | C[2] + R[2][0] * x + R[2][1] * y + R[2][2] * z
256 | ]);
257 | break;
258 | default:
259 | return null;
260 | }
261 | },
262 |
263 | // Returns the result of reflecting the point in the given point, line or plane
264 | reflectionIn: function(obj) {
265 | if (obj.anchor) {
266 | // obj is a plane or line
267 | var P = this.elements.slice();
268 | var C = obj.pointClosestTo(P).elements;
269 | return Vector.create([C[0] + (C[0] - P[0]), C[1] + (C[1] - P[1]), C[2] + (C[2] - (P[2] || 0))]);
270 | } else {
271 | // obj is a point
272 | var Q = obj.elements || obj;
273 | if (this.elements.length != Q.length) { return null; }
274 | return this.map(function(x, i) { return Q[i-1] + (Q[i-1] - x); });
275 | }
276 | },
277 |
278 | // Utility to make sure vectors are 3D. If they are 2D, a zero z-component is added
279 | to3D: function() {
280 | var V = this.dup();
281 | switch (V.elements.length) {
282 | case 3: break;
283 | case 2: V.elements.push(0); break;
284 | default: return null;
285 | }
286 | return V;
287 | },
288 |
289 | // Returns a string representation of the vector
290 | inspect: function() {
291 | return '[' + this.elements.join(', ') + ']';
292 | },
293 |
294 | // Set vector's elements from an array
295 | setElements: function(els) {
296 | this.elements = (els.elements || els).slice();
297 | return this;
298 | }
299 | };
300 |
301 | // Constructor function
302 | Vector.create = function(elements) {
303 | var V = new Vector();
304 | return V.setElements(elements);
305 | };
306 |
307 | // i, j, k unit vectors
308 | Vector.i = Vector.create([1,0,0]);
309 | Vector.j = Vector.create([0,1,0]);
310 | Vector.k = Vector.create([0,0,1]);
311 |
312 | // Random vector of size n
313 | Vector.Random = function(n) {
314 | var elements = [];
315 | do { elements.push(Math.random());
316 | } while (--n);
317 | return Vector.create(elements);
318 | };
319 |
320 | // Vector filled with zeros
321 | Vector.Zero = function(n) {
322 | var elements = [];
323 | do { elements.push(0);
324 | } while (--n);
325 | return Vector.create(elements);
326 | };
327 |
328 |
329 |
330 | function Matrix() {}
331 | Matrix.prototype = {
332 |
333 | // Returns element (i,j) of the matrix
334 | e: function(i,j) {
335 | if (i < 1 || i > this.elements.length || j < 1 || j > this.elements[0].length) { return null; }
336 | return this.elements[i-1][j-1];
337 | },
338 |
339 | // Returns row k of the matrix as a vector
340 | row: function(i) {
341 | if (i > this.elements.length) { return null; }
342 | return Vector.create(this.elements[i-1]);
343 | },
344 |
345 | // Returns column k of the matrix as a vector
346 | col: function(j) {
347 | if (j > this.elements[0].length) { return null; }
348 | var col = [], n = this.elements.length, k = n, i;
349 | do { i = k - n;
350 | col.push(this.elements[i][j-1]);
351 | } while (--n);
352 | return Vector.create(col);
353 | },
354 |
355 | // Returns the number of rows/columns the matrix has
356 | dimensions: function() {
357 | return {rows: this.elements.length, cols: this.elements[0].length};
358 | },
359 |
360 | // Returns the number of rows in the matrix
361 | rows: function() {
362 | return this.elements.length;
363 | },
364 |
365 | // Returns the number of columns in the matrix
366 | cols: function() {
367 | return this.elements[0].length;
368 | },
369 |
370 | // Returns true iff the matrix is equal to the argument. You can supply
371 | // a vector as the argument, in which case the receiver must be a
372 | // one-column matrix equal to the vector.
373 | eql: function(matrix) {
374 | var M = matrix.elements || matrix;
375 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
376 | if (this.elements.length != M.length ||
377 | this.elements[0].length != M[0].length) { return false; }
378 | var ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
379 | do { i = ki - ni;
380 | nj = kj;
381 | do { j = kj - nj;
382 | if (Math.abs(this.elements[i][j] - M[i][j]) > Sylvester.precision) { return false; }
383 | } while (--nj);
384 | } while (--ni);
385 | return true;
386 | },
387 |
388 | // Returns a copy of the matrix
389 | dup: function() {
390 | return Matrix.create(this.elements);
391 | },
392 |
393 | // Maps the matrix to another matrix (of the same dimensions) according to the given function
394 | map: function(fn) {
395 | var els = [], ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
396 | do { i = ki - ni;
397 | nj = kj;
398 | els[i] = [];
399 | do { j = kj - nj;
400 | els[i][j] = fn(this.elements[i][j], i + 1, j + 1);
401 | } while (--nj);
402 | } while (--ni);
403 | return Matrix.create(els);
404 | },
405 |
406 | // Returns true iff the argument has the same dimensions as the matrix
407 | isSameSizeAs: function(matrix) {
408 | var M = matrix.elements || matrix;
409 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
410 | return (this.elements.length == M.length &&
411 | this.elements[0].length == M[0].length);
412 | },
413 |
414 | // Returns the result of adding the argument to the matrix
415 | add: function(matrix) {
416 | var M = matrix.elements || matrix;
417 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
418 | if (!this.isSameSizeAs(M)) { return null; }
419 | return this.map(function(x, i, j) { return x + M[i-1][j-1]; });
420 | },
421 |
422 | // Returns the result of subtracting the argument from the matrix
423 | subtract: function(matrix) {
424 | var M = matrix.elements || matrix;
425 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
426 | if (!this.isSameSizeAs(M)) { return null; }
427 | return this.map(function(x, i, j) { return x - M[i-1][j-1]; });
428 | },
429 |
430 | // Returns true iff the matrix can multiply the argument from the left
431 | canMultiplyFromLeft: function(matrix) {
432 | var M = matrix.elements || matrix;
433 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
434 | // this.columns should equal matrix.rows
435 | return (this.elements[0].length == M.length);
436 | },
437 |
438 | // Returns the result of multiplying the matrix from the right by the argument.
439 | // If the argument is a scalar then just multiply all the elements. If the argument is
440 | // a vector, a vector is returned, which saves you having to remember calling
441 | // col(1) on the result.
442 | multiply: function(matrix) {
443 | if (!matrix.elements) {
444 | return this.map(function(x) { return x * matrix; });
445 | }
446 | var returnVector = matrix.modulus ? true : false;
447 | var M = matrix.elements || matrix;
448 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
449 | if (!this.canMultiplyFromLeft(M)) { return null; }
450 | var ni = this.elements.length, ki = ni, i, nj, kj = M[0].length, j;
451 | var cols = this.elements[0].length, elements = [], sum, nc, c;
452 | do { i = ki - ni;
453 | elements[i] = [];
454 | nj = kj;
455 | do { j = kj - nj;
456 | sum = 0;
457 | nc = cols;
458 | do { c = cols - nc;
459 | sum += this.elements[i][c] * M[c][j];
460 | } while (--nc);
461 | elements[i][j] = sum;
462 | } while (--nj);
463 | } while (--ni);
464 | var M = Matrix.create(elements);
465 | return returnVector ? M.col(1) : M;
466 | },
467 |
468 | x: function(matrix) { return this.multiply(matrix); },
469 |
470 | // Returns a submatrix taken from the matrix
471 | // Argument order is: start row, start col, nrows, ncols
472 | // Element selection wraps if the required index is outside the matrix's bounds, so you could
473 | // use this to perform row/column cycling or copy-augmenting.
474 | minor: function(a, b, c, d) {
475 | var elements = [], ni = c, i, nj, j;
476 | var rows = this.elements.length, cols = this.elements[0].length;
477 | do { i = c - ni;
478 | elements[i] = [];
479 | nj = d;
480 | do { j = d - nj;
481 | elements[i][j] = this.elements[(a+i-1)%rows][(b+j-1)%cols];
482 | } while (--nj);
483 | } while (--ni);
484 | return Matrix.create(elements);
485 | },
486 |
487 | // Returns the transpose of the matrix
488 | transpose: function() {
489 | var rows = this.elements.length, cols = this.elements[0].length;
490 | var elements = [], ni = cols, i, nj, j;
491 | do { i = cols - ni;
492 | elements[i] = [];
493 | nj = rows;
494 | do { j = rows - nj;
495 | elements[i][j] = this.elements[j][i];
496 | } while (--nj);
497 | } while (--ni);
498 | return Matrix.create(elements);
499 | },
500 |
501 | // Returns true iff the matrix is square
502 | isSquare: function() {
503 | return (this.elements.length == this.elements[0].length);
504 | },
505 |
506 | // Returns the (absolute) largest element of the matrix
507 | max: function() {
508 | var m = 0, ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
509 | do { i = ki - ni;
510 | nj = kj;
511 | do { j = kj - nj;
512 | if (Math.abs(this.elements[i][j]) > Math.abs(m)) { m = this.elements[i][j]; }
513 | } while (--nj);
514 | } while (--ni);
515 | return m;
516 | },
517 |
518 | // Returns the indeces of the first match found by reading row-by-row from left to right
519 | indexOf: function(x) {
520 | var index = null, ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
521 | do { i = ki - ni;
522 | nj = kj;
523 | do { j = kj - nj;
524 | if (this.elements[i][j] == x) { return {i: i+1, j: j+1}; }
525 | } while (--nj);
526 | } while (--ni);
527 | return null;
528 | },
529 |
530 | // If the matrix is square, returns the diagonal elements as a vector.
531 | // Otherwise, returns null.
532 | diagonal: function() {
533 | if (!this.isSquare) { return null; }
534 | var els = [], n = this.elements.length, k = n, i;
535 | do { i = k - n;
536 | els.push(this.elements[i][i]);
537 | } while (--n);
538 | return Vector.create(els);
539 | },
540 |
541 | // Make the matrix upper (right) triangular by Gaussian elimination.
542 | // This method only adds multiples of rows to other rows. No rows are
543 | // scaled up or switched, and the determinant is preserved.
544 | toRightTriangular: function() {
545 | var M = this.dup(), els;
546 | var n = this.elements.length, k = n, i, np, kp = this.elements[0].length, p;
547 | do { i = k - n;
548 | if (M.elements[i][i] == 0) {
549 | for (j = i + 1; j < k; j++) {
550 | if (M.elements[j][i] != 0) {
551 | els = []; np = kp;
552 | do { p = kp - np;
553 | els.push(M.elements[i][p] + M.elements[j][p]);
554 | } while (--np);
555 | M.elements[i] = els;
556 | break;
557 | }
558 | }
559 | }
560 | if (M.elements[i][i] != 0) {
561 | for (j = i + 1; j < k; j++) {
562 | var multiplier = M.elements[j][i] / M.elements[i][i];
563 | els = []; np = kp;
564 | do { p = kp - np;
565 | // Elements with column numbers up to an including the number
566 | // of the row that we're subtracting can safely be set straight to
567 | // zero, since that's the point of this routine and it avoids having
568 | // to loop over and correct rounding errors later
569 | els.push(p <= i ? 0 : M.elements[j][p] - M.elements[i][p] * multiplier);
570 | } while (--np);
571 | M.elements[j] = els;
572 | }
573 | }
574 | } while (--n);
575 | return M;
576 | },
577 |
578 | toUpperTriangular: function() { return this.toRightTriangular(); },
579 |
580 | // Returns the determinant for square matrices
581 | determinant: function() {
582 | if (!this.isSquare()) { return null; }
583 | var M = this.toRightTriangular();
584 | var det = M.elements[0][0], n = M.elements.length - 1, k = n, i;
585 | do { i = k - n + 1;
586 | det = det * M.elements[i][i];
587 | } while (--n);
588 | return det;
589 | },
590 |
591 | det: function() { return this.determinant(); },
592 |
593 | // Returns true iff the matrix is singular
594 | isSingular: function() {
595 | return (this.isSquare() && this.determinant() === 0);
596 | },
597 |
598 | // Returns the trace for square matrices
599 | trace: function() {
600 | if (!this.isSquare()) { return null; }
601 | var tr = this.elements[0][0], n = this.elements.length - 1, k = n, i;
602 | do { i = k - n + 1;
603 | tr += this.elements[i][i];
604 | } while (--n);
605 | return tr;
606 | },
607 |
608 | tr: function() { return this.trace(); },
609 |
610 | // Returns the rank of the matrix
611 | rank: function() {
612 | var M = this.toRightTriangular(), rank = 0;
613 | var ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
614 | do { i = ki - ni;
615 | nj = kj;
616 | do { j = kj - nj;
617 | if (Math.abs(M.elements[i][j]) > Sylvester.precision) { rank++; break; }
618 | } while (--nj);
619 | } while (--ni);
620 | return rank;
621 | },
622 |
623 | rk: function() { return this.rank(); },
624 |
625 | // Returns the result of attaching the given argument to the right-hand side of the matrix
626 | augment: function(matrix) {
627 | var M = matrix.elements || matrix;
628 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
629 | var T = this.dup(), cols = T.elements[0].length;
630 | var ni = T.elements.length, ki = ni, i, nj, kj = M[0].length, j;
631 | if (ni != M.length) { return null; }
632 | do { i = ki - ni;
633 | nj = kj;
634 | do { j = kj - nj;
635 | T.elements[i][cols + j] = M[i][j];
636 | } while (--nj);
637 | } while (--ni);
638 | return T;
639 | },
640 |
641 | // Returns the inverse (if one exists) using Gauss-Jordan
642 | inverse: function() {
643 | if (!this.isSquare() || this.isSingular()) { return null; }
644 | var ni = this.elements.length, ki = ni, i, j;
645 | var M = this.augment(Matrix.I(ni)).toRightTriangular();
646 | var np, kp = M.elements[0].length, p, els, divisor;
647 | var inverse_elements = [], new_element;
648 | // Matrix is non-singular so there will be no zeros on the diagonal
649 | // Cycle through rows from last to first
650 | do { i = ni - 1;
651 | // First, normalise diagonal elements to 1
652 | els = []; np = kp;
653 | inverse_elements[i] = [];
654 | divisor = M.elements[i][i];
655 | do { p = kp - np;
656 | new_element = M.elements[i][p] / divisor;
657 | els.push(new_element);
658 | // Shuffle of the current row of the right hand side into the results
659 | // array as it will not be modified by later runs through this loop
660 | if (p >= ki) { inverse_elements[i].push(new_element); }
661 | } while (--np);
662 | M.elements[i] = els;
663 | // Then, subtract this row from those above it to
664 | // give the identity matrix on the left hand side
665 | for (j = 0; j < i; j++) {
666 | els = []; np = kp;
667 | do { p = kp - np;
668 | els.push(M.elements[j][p] - M.elements[i][p] * M.elements[j][i]);
669 | } while (--np);
670 | M.elements[j] = els;
671 | }
672 | } while (--ni);
673 | return Matrix.create(inverse_elements);
674 | },
675 |
676 | inv: function() { return this.inverse(); },
677 |
678 | // Returns the result of rounding all the elements
679 | round: function() {
680 | return this.map(function(x) { return Math.round(x); });
681 | },
682 |
683 | // Returns a copy of the matrix with elements set to the given value if they
684 | // differ from it by less than Sylvester.precision
685 | snapTo: function(x) {
686 | return this.map(function(p) {
687 | return (Math.abs(p - x) <= Sylvester.precision) ? x : p;
688 | });
689 | },
690 |
691 | // Returns a string representation of the matrix
692 | inspect: function() {
693 | var matrix_rows = [];
694 | var n = this.elements.length, k = n, i;
695 | do { i = k - n;
696 | matrix_rows.push(Vector.create(this.elements[i]).inspect());
697 | } while (--n);
698 | return matrix_rows.join('\n');
699 | },
700 |
701 | // Set the matrix's elements from an array. If the argument passed
702 | // is a vector, the resulting matrix will be a single column.
703 | setElements: function(els) {
704 | var i, elements = els.elements || els;
705 | if (typeof(elements[0][0]) != 'undefined') {
706 | var ni = elements.length, ki = ni, nj, kj, j;
707 | this.elements = [];
708 | do { i = ki - ni;
709 | nj = elements[i].length; kj = nj;
710 | this.elements[i] = [];
711 | do { j = kj - nj;
712 | this.elements[i][j] = elements[i][j];
713 | } while (--nj);
714 | } while(--ni);
715 | return this;
716 | }
717 | var n = elements.length, k = n;
718 | this.elements = [];
719 | do { i = k - n;
720 | this.elements.push([elements[i]]);
721 | } while (--n);
722 | return this;
723 | }
724 | };
725 |
726 | // Constructor function
727 | Matrix.create = function(elements) {
728 | var M = new Matrix();
729 | return M.setElements(elements);
730 | };
731 |
732 | // Identity matrix of size n
733 | Matrix.I = function(n) {
734 | var els = [], k = n, i, nj, j;
735 | do { i = k - n;
736 | els[i] = []; nj = k;
737 | do { j = k - nj;
738 | els[i][j] = (i == j) ? 1 : 0;
739 | } while (--nj);
740 | } while (--n);
741 | return Matrix.create(els);
742 | };
743 |
744 | // Diagonal matrix - all off-diagonal elements are zero
745 | Matrix.Diagonal = function(elements) {
746 | var n = elements.length, k = n, i;
747 | var M = Matrix.I(n);
748 | do { i = k - n;
749 | M.elements[i][i] = elements[i];
750 | } while (--n);
751 | return M;
752 | };
753 |
754 | // Rotation matrix about some axis. If no axis is
755 | // supplied, assume we're after a 2D transform
756 | Matrix.Rotation = function(theta, a) {
757 | if (!a) {
758 | return Matrix.create([
759 | [Math.cos(theta), -Math.sin(theta)],
760 | [Math.sin(theta), Math.cos(theta)]
761 | ]);
762 | }
763 | var axis = a.dup();
764 | if (axis.elements.length != 3) { return null; }
765 | var mod = axis.modulus();
766 | var x = axis.elements[0]/mod, y = axis.elements[1]/mod, z = axis.elements[2]/mod;
767 | var s = Math.sin(theta), c = Math.cos(theta), t = 1 - c;
768 | // Formula derived here: http://www.gamedev.net/reference/articles/article1199.asp
769 | // That proof rotates the co-ordinate system so theta
770 | // becomes -theta and sin becomes -sin here.
771 | return Matrix.create([
772 | [ t*x*x + c, t*x*y - s*z, t*x*z + s*y ],
773 | [ t*x*y + s*z, t*y*y + c, t*y*z - s*x ],
774 | [ t*x*z - s*y, t*y*z + s*x, t*z*z + c ]
775 | ]);
776 | };
777 |
778 | // Special case rotations
779 | Matrix.RotationX = function(t) {
780 | var c = Math.cos(t), s = Math.sin(t);
781 | return Matrix.create([
782 | [ 1, 0, 0 ],
783 | [ 0, c, -s ],
784 | [ 0, s, c ]
785 | ]);
786 | };
787 | Matrix.RotationY = function(t) {
788 | var c = Math.cos(t), s = Math.sin(t);
789 | return Matrix.create([
790 | [ c, 0, s ],
791 | [ 0, 1, 0 ],
792 | [ -s, 0, c ]
793 | ]);
794 | };
795 | Matrix.RotationZ = function(t) {
796 | var c = Math.cos(t), s = Math.sin(t);
797 | return Matrix.create([
798 | [ c, -s, 0 ],
799 | [ s, c, 0 ],
800 | [ 0, 0, 1 ]
801 | ]);
802 | };
803 |
804 | // Random matrix of n rows, m columns
805 | Matrix.Random = function(n, m) {
806 | return Matrix.Zero(n, m).map(
807 | function() { return Math.random(); }
808 | );
809 | };
810 |
811 | // Matrix filled with zeros
812 | Matrix.Zero = function(n, m) {
813 | var els = [], ni = n, i, nj, j;
814 | do { i = n - ni;
815 | els[i] = [];
816 | nj = m;
817 | do { j = m - nj;
818 | els[i][j] = 0;
819 | } while (--nj);
820 | } while (--ni);
821 | return Matrix.create(els);
822 | };
823 |
824 |
825 |
826 | function Line() {}
827 | Line.prototype = {
828 |
829 | // Returns true if the argument occupies the same space as the line
830 | eql: function(line) {
831 | return (this.isParallelTo(line) && this.contains(line.anchor));
832 | },
833 |
834 | // Returns a copy of the line
835 | dup: function() {
836 | return Line.create(this.anchor, this.direction);
837 | },
838 |
839 | // Returns the result of translating the line by the given vector/array
840 | translate: function(vector) {
841 | var V = vector.elements || vector;
842 | return Line.create([
843 | this.anchor.elements[0] + V[0],
844 | this.anchor.elements[1] + V[1],
845 | this.anchor.elements[2] + (V[2] || 0)
846 | ], this.direction);
847 | },
848 |
849 | // Returns true if the line is parallel to the argument. Here, 'parallel to'
850 | // means that the argument's direction is either parallel or antiparallel to
851 | // the line's own direction. A line is parallel to a plane if the two do not
852 | // have a unique intersection.
853 | isParallelTo: function(obj) {
854 | if (obj.normal) { return obj.isParallelTo(this); }
855 | var theta = this.direction.angleFrom(obj.direction);
856 | return (Math.abs(theta) <= Sylvester.precision || Math.abs(theta - Math.PI) <= Sylvester.precision);
857 | },
858 |
859 | // Returns the line's perpendicular distance from the argument,
860 | // which can be a point, a line or a plane
861 | distanceFrom: function(obj) {
862 | if (obj.normal) { return obj.distanceFrom(this); }
863 | if (obj.direction) {
864 | // obj is a line
865 | if (this.isParallelTo(obj)) { return this.distanceFrom(obj.anchor); }
866 | var N = this.direction.cross(obj.direction).toUnitVector().elements;
867 | var A = this.anchor.elements, B = obj.anchor.elements;
868 | return Math.abs((A[0] - B[0]) * N[0] + (A[1] - B[1]) * N[1] + (A[2] - B[2]) * N[2]);
869 | } else {
870 | // obj is a point
871 | var P = obj.elements || obj;
872 | var A = this.anchor.elements, D = this.direction.elements;
873 | var PA1 = P[0] - A[0], PA2 = P[1] - A[1], PA3 = (P[2] || 0) - A[2];
874 | var modPA = Math.sqrt(PA1*PA1 + PA2*PA2 + PA3*PA3);
875 | if (modPA === 0) return 0;
876 | // Assumes direction vector is normalized
877 | var cosTheta = (PA1 * D[0] + PA2 * D[1] + PA3 * D[2]) / modPA;
878 | var sin2 = 1 - cosTheta*cosTheta;
879 | return Math.abs(modPA * Math.sqrt(sin2 < 0 ? 0 : sin2));
880 | }
881 | },
882 |
883 | // Returns true iff the argument is a point on the line
884 | contains: function(point) {
885 | var dist = this.distanceFrom(point);
886 | return (dist !== null && dist <= Sylvester.precision);
887 | },
888 |
889 | // Returns true iff the line lies in the given plane
890 | liesIn: function(plane) {
891 | return plane.contains(this);
892 | },
893 |
894 | // Returns true iff the line has a unique point of intersection with the argument
895 | intersects: function(obj) {
896 | if (obj.normal) { return obj.intersects(this); }
897 | return (!this.isParallelTo(obj) && this.distanceFrom(obj) <= Sylvester.precision);
898 | },
899 |
900 | // Returns the unique intersection point with the argument, if one exists
901 | intersectionWith: function(obj) {
902 | if (obj.normal) { return obj.intersectionWith(this); }
903 | if (!this.intersects(obj)) { return null; }
904 | var P = this.anchor.elements, X = this.direction.elements,
905 | Q = obj.anchor.elements, Y = obj.direction.elements;
906 | var X1 = X[0], X2 = X[1], X3 = X[2], Y1 = Y[0], Y2 = Y[1], Y3 = Y[2];
907 | var PsubQ1 = P[0] - Q[0], PsubQ2 = P[1] - Q[1], PsubQ3 = P[2] - Q[2];
908 | var XdotQsubP = - X1*PsubQ1 - X2*PsubQ2 - X3*PsubQ3;
909 | var YdotPsubQ = Y1*PsubQ1 + Y2*PsubQ2 + Y3*PsubQ3;
910 | var XdotX = X1*X1 + X2*X2 + X3*X3;
911 | var YdotY = Y1*Y1 + Y2*Y2 + Y3*Y3;
912 | var XdotY = X1*Y1 + X2*Y2 + X3*Y3;
913 | var k = (XdotQsubP * YdotY / XdotX + XdotY * YdotPsubQ) / (YdotY - XdotY * XdotY);
914 | return Vector.create([P[0] + k*X1, P[1] + k*X2, P[2] + k*X3]);
915 | },
916 |
917 | // Returns the point on the line that is closest to the given point or line
918 | pointClosestTo: function(obj) {
919 | if (obj.direction) {
920 | // obj is a line
921 | if (this.intersects(obj)) { return this.intersectionWith(obj); }
922 | if (this.isParallelTo(obj)) { return null; }
923 | var D = this.direction.elements, E = obj.direction.elements;
924 | var D1 = D[0], D2 = D[1], D3 = D[2], E1 = E[0], E2 = E[1], E3 = E[2];
925 | // Create plane containing obj and the shared normal and intersect this with it
926 | // Thank you: http://www.cgafaq.info/wiki/Line-line_distance
927 | var x = (D3 * E1 - D1 * E3), y = (D1 * E2 - D2 * E1), z = (D2 * E3 - D3 * E2);
928 | var N = Vector.create([x * E3 - y * E2, y * E1 - z * E3, z * E2 - x * E1]);
929 | var P = Plane.create(obj.anchor, N);
930 | return P.intersectionWith(this);
931 | } else {
932 | // obj is a point
933 | var P = obj.elements || obj;
934 | if (this.contains(P)) { return Vector.create(P); }
935 | var A = this.anchor.elements, D = this.direction.elements;
936 | var D1 = D[0], D2 = D[1], D3 = D[2], A1 = A[0], A2 = A[1], A3 = A[2];
937 | var x = D1 * (P[1]-A2) - D2 * (P[0]-A1), y = D2 * ((P[2] || 0) - A3) - D3 * (P[1]-A2),
938 | z = D3 * (P[0]-A1) - D1 * ((P[2] || 0) - A3);
939 | var V = Vector.create([D2 * x - D3 * z, D3 * y - D1 * x, D1 * z - D2 * y]);
940 | var k = this.distanceFrom(P) / V.modulus();
941 | return Vector.create([
942 | P[0] + V.elements[0] * k,
943 | P[1] + V.elements[1] * k,
944 | (P[2] || 0) + V.elements[2] * k
945 | ]);
946 | }
947 | },
948 |
949 | // Returns a copy of the line rotated by t radians about the given line. Works by
950 | // finding the argument's closest point to this line's anchor point (call this C) and
951 | // rotating the anchor about C. Also rotates the line's direction about the argument's.
952 | // Be careful with this - the rotation axis' direction affects the outcome!
953 | rotate: function(t, line) {
954 | // If we're working in 2D
955 | if (typeof(line.direction) == 'undefined') { line = Line.create(line.to3D(), Vector.k); }
956 | var R = Matrix.Rotation(t, line.direction).elements;
957 | var C = line.pointClosestTo(this.anchor).elements;
958 | var A = this.anchor.elements, D = this.direction.elements;
959 | var C1 = C[0], C2 = C[1], C3 = C[2], A1 = A[0], A2 = A[1], A3 = A[2];
960 | var x = A1 - C1, y = A2 - C2, z = A3 - C3;
961 | return Line.create([
962 | C1 + R[0][0] * x + R[0][1] * y + R[0][2] * z,
963 | C2 + R[1][0] * x + R[1][1] * y + R[1][2] * z,
964 | C3 + R[2][0] * x + R[2][1] * y + R[2][2] * z
965 | ], [
966 | R[0][0] * D[0] + R[0][1] * D[1] + R[0][2] * D[2],
967 | R[1][0] * D[0] + R[1][1] * D[1] + R[1][2] * D[2],
968 | R[2][0] * D[0] + R[2][1] * D[1] + R[2][2] * D[2]
969 | ]);
970 | },
971 |
972 | // Returns the line's reflection in the given point or line
973 | reflectionIn: function(obj) {
974 | if (obj.normal) {
975 | // obj is a plane
976 | var A = this.anchor.elements, D = this.direction.elements;
977 | var A1 = A[0], A2 = A[1], A3 = A[2], D1 = D[0], D2 = D[1], D3 = D[2];
978 | var newA = this.anchor.reflectionIn(obj).elements;
979 | // Add the line's direction vector to its anchor, then mirror that in the plane
980 | var AD1 = A1 + D1, AD2 = A2 + D2, AD3 = A3 + D3;
981 | var Q = obj.pointClosestTo([AD1, AD2, AD3]).elements;
982 | var newD = [Q[0] + (Q[0] - AD1) - newA[0], Q[1] + (Q[1] - AD2) - newA[1], Q[2] + (Q[2] - AD3) - newA[2]];
983 | return Line.create(newA, newD);
984 | } else if (obj.direction) {
985 | // obj is a line - reflection obtained by rotating PI radians about obj
986 | return this.rotate(Math.PI, obj);
987 | } else {
988 | // obj is a point - just reflect the line's anchor in it
989 | var P = obj.elements || obj;
990 | return Line.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.direction);
991 | }
992 | },
993 |
994 | // Set the line's anchor point and direction.
995 | setVectors: function(anchor, direction) {
996 | // Need to do this so that line's properties are not
997 | // references to the arguments passed in
998 | anchor = Vector.create(anchor);
999 | direction = Vector.create(direction);
1000 | if (anchor.elements.length == 2) {anchor.elements.push(0); }
1001 | if (direction.elements.length == 2) { direction.elements.push(0); }
1002 | if (anchor.elements.length > 3 || direction.elements.length > 3) { return null; }
1003 | var mod = direction.modulus();
1004 | if (mod === 0) { return null; }
1005 | this.anchor = anchor;
1006 | this.direction = Vector.create([
1007 | direction.elements[0] / mod,
1008 | direction.elements[1] / mod,
1009 | direction.elements[2] / mod
1010 | ]);
1011 | return this;
1012 | }
1013 | };
1014 |
1015 |
1016 | // Constructor function
1017 | Line.create = function(anchor, direction) {
1018 | var L = new Line();
1019 | return L.setVectors(anchor, direction);
1020 | };
1021 |
1022 | // Axes
1023 | Line.X = Line.create(Vector.Zero(3), Vector.i);
1024 | Line.Y = Line.create(Vector.Zero(3), Vector.j);
1025 | Line.Z = Line.create(Vector.Zero(3), Vector.k);
1026 |
1027 |
1028 |
1029 | function Plane() {}
1030 | Plane.prototype = {
1031 |
1032 | // Returns true iff the plane occupies the same space as the argument
1033 | eql: function(plane) {
1034 | return (this.contains(plane.anchor) && this.isParallelTo(plane));
1035 | },
1036 |
1037 | // Returns a copy of the plane
1038 | dup: function() {
1039 | return Plane.create(this.anchor, this.normal);
1040 | },
1041 |
1042 | // Returns the result of translating the plane by the given vector
1043 | translate: function(vector) {
1044 | var V = vector.elements || vector;
1045 | return Plane.create([
1046 | this.anchor.elements[0] + V[0],
1047 | this.anchor.elements[1] + V[1],
1048 | this.anchor.elements[2] + (V[2] || 0)
1049 | ], this.normal);
1050 | },
1051 |
1052 | // Returns true iff the plane is parallel to the argument. Will return true
1053 | // if the planes are equal, or if you give a line and it lies in the plane.
1054 | isParallelTo: function(obj) {
1055 | var theta;
1056 | if (obj.normal) {
1057 | // obj is a plane
1058 | theta = this.normal.angleFrom(obj.normal);
1059 | return (Math.abs(theta) <= Sylvester.precision || Math.abs(Math.PI - theta) <= Sylvester.precision);
1060 | } else if (obj.direction) {
1061 | // obj is a line
1062 | return this.normal.isPerpendicularTo(obj.direction);
1063 | }
1064 | return null;
1065 | },
1066 |
1067 | // Returns true iff the receiver is perpendicular to the argument
1068 | isPerpendicularTo: function(plane) {
1069 | var theta = this.normal.angleFrom(plane.normal);
1070 | return (Math.abs(Math.PI/2 - theta) <= Sylvester.precision);
1071 | },
1072 |
1073 | // Returns the plane's distance from the given object (point, line or plane)
1074 | distanceFrom: function(obj) {
1075 | if (this.intersects(obj) || this.contains(obj)) { return 0; }
1076 | if (obj.anchor) {
1077 | // obj is a plane or line
1078 | var A = this.anchor.elements, B = obj.anchor.elements, N = this.normal.elements;
1079 | return Math.abs((A[0] - B[0]) * N[0] + (A[1] - B[1]) * N[1] + (A[2] - B[2]) * N[2]);
1080 | } else {
1081 | // obj is a point
1082 | var P = obj.elements || obj;
1083 | var A = this.anchor.elements, N = this.normal.elements;
1084 | return Math.abs((A[0] - P[0]) * N[0] + (A[1] - P[1]) * N[1] + (A[2] - (P[2] || 0)) * N[2]);
1085 | }
1086 | },
1087 |
1088 | // Returns true iff the plane contains the given point or line
1089 | contains: function(obj) {
1090 | if (obj.normal) { return null; }
1091 | if (obj.direction) {
1092 | return (this.contains(obj.anchor) && this.contains(obj.anchor.add(obj.direction)));
1093 | } else {
1094 | var P = obj.elements || obj;
1095 | var A = this.anchor.elements, N = this.normal.elements;
1096 | var diff = Math.abs(N[0]*(A[0] - P[0]) + N[1]*(A[1] - P[1]) + N[2]*(A[2] - (P[2] || 0)));
1097 | return (diff <= Sylvester.precision);
1098 | }
1099 | },
1100 |
1101 | // Returns true iff the plane has a unique point/line of intersection with the argument
1102 | intersects: function(obj) {
1103 | if (typeof(obj.direction) == 'undefined' && typeof(obj.normal) == 'undefined') { return null; }
1104 | return !this.isParallelTo(obj);
1105 | },
1106 |
1107 | // Returns the unique intersection with the argument, if one exists. The result
1108 | // will be a vector if a line is supplied, and a line if a plane is supplied.
1109 | intersectionWith: function(obj) {
1110 | if (!this.intersects(obj)) { return null; }
1111 | if (obj.direction) {
1112 | // obj is a line
1113 | var A = obj.anchor.elements, D = obj.direction.elements,
1114 | P = this.anchor.elements, N = this.normal.elements;
1115 | var multiplier = (N[0]*(P[0]-A[0]) + N[1]*(P[1]-A[1]) + N[2]*(P[2]-A[2])) / (N[0]*D[0] + N[1]*D[1] + N[2]*D[2]);
1116 | return Vector.create([A[0] + D[0]*multiplier, A[1] + D[1]*multiplier, A[2] + D[2]*multiplier]);
1117 | } else if (obj.normal) {
1118 | // obj is a plane
1119 | var direction = this.normal.cross(obj.normal).toUnitVector();
1120 | // To find an anchor point, we find one co-ordinate that has a value
1121 | // of zero somewhere on the intersection, and remember which one we picked
1122 | var N = this.normal.elements, A = this.anchor.elements,
1123 | O = obj.normal.elements, B = obj.anchor.elements;
1124 | var solver = Matrix.Zero(2,2), i = 0;
1125 | while (solver.isSingular()) {
1126 | i++;
1127 | solver = Matrix.create([
1128 | [ N[i%3], N[(i+1)%3] ],
1129 | [ O[i%3], O[(i+1)%3] ]
1130 | ]);
1131 | }
1132 | // Then we solve the simultaneous equations in the remaining dimensions
1133 | var inverse = solver.inverse().elements;
1134 | var x = N[0]*A[0] + N[1]*A[1] + N[2]*A[2];
1135 | var y = O[0]*B[0] + O[1]*B[1] + O[2]*B[2];
1136 | var intersection = [
1137 | inverse[0][0] * x + inverse[0][1] * y,
1138 | inverse[1][0] * x + inverse[1][1] * y
1139 | ];
1140 | var anchor = [];
1141 | for (var j = 1; j <= 3; j++) {
1142 | // This formula picks the right element from intersection by
1143 | // cycling depending on which element we set to zero above
1144 | anchor.push((i == j) ? 0 : intersection[(j + (5 - i)%3)%3]);
1145 | }
1146 | return Line.create(anchor, direction);
1147 | }
1148 | },
1149 |
1150 | // Returns the point in the plane closest to the given point
1151 | pointClosestTo: function(point) {
1152 | var P = point.elements || point;
1153 | var A = this.anchor.elements, N = this.normal.elements;
1154 | var dot = (A[0] - P[0]) * N[0] + (A[1] - P[1]) * N[1] + (A[2] - (P[2] || 0)) * N[2];
1155 | return Vector.create([P[0] + N[0] * dot, P[1] + N[1] * dot, (P[2] || 0) + N[2] * dot]);
1156 | },
1157 |
1158 | // Returns a copy of the plane, rotated by t radians about the given line
1159 | // See notes on Line#rotate.
1160 | rotate: function(t, line) {
1161 | var R = Matrix.Rotation(t, line.direction).elements;
1162 | var C = line.pointClosestTo(this.anchor).elements;
1163 | var A = this.anchor.elements, N = this.normal.elements;
1164 | var C1 = C[0], C2 = C[1], C3 = C[2], A1 = A[0], A2 = A[1], A3 = A[2];
1165 | var x = A1 - C1, y = A2 - C2, z = A3 - C3;
1166 | return Plane.create([
1167 | C1 + R[0][0] * x + R[0][1] * y + R[0][2] * z,
1168 | C2 + R[1][0] * x + R[1][1] * y + R[1][2] * z,
1169 | C3 + R[2][0] * x + R[2][1] * y + R[2][2] * z
1170 | ], [
1171 | R[0][0] * N[0] + R[0][1] * N[1] + R[0][2] * N[2],
1172 | R[1][0] * N[0] + R[1][1] * N[1] + R[1][2] * N[2],
1173 | R[2][0] * N[0] + R[2][1] * N[1] + R[2][2] * N[2]
1174 | ]);
1175 | },
1176 |
1177 | // Returns the reflection of the plane in the given point, line or plane.
1178 | reflectionIn: function(obj) {
1179 | if (obj.normal) {
1180 | // obj is a plane
1181 | var A = this.anchor.elements, N = this.normal.elements;
1182 | var A1 = A[0], A2 = A[1], A3 = A[2], N1 = N[0], N2 = N[1], N3 = N[2];
1183 | var newA = this.anchor.reflectionIn(obj).elements;
1184 | // Add the plane's normal to its anchor, then mirror that in the other plane
1185 | var AN1 = A1 + N1, AN2 = A2 + N2, AN3 = A3 + N3;
1186 | var Q = obj.pointClosestTo([AN1, AN2, AN3]).elements;
1187 | var newN = [Q[0] + (Q[0] - AN1) - newA[0], Q[1] + (Q[1] - AN2) - newA[1], Q[2] + (Q[2] - AN3) - newA[2]];
1188 | return Plane.create(newA, newN);
1189 | } else if (obj.direction) {
1190 | // obj is a line
1191 | return this.rotate(Math.PI, obj);
1192 | } else {
1193 | // obj is a point
1194 | var P = obj.elements || obj;
1195 | return Plane.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.normal);
1196 | }
1197 | },
1198 |
1199 | // Sets the anchor point and normal to the plane. If three arguments are specified,
1200 | // the normal is calculated by assuming the three points should lie in the same plane.
1201 | // If only two are sepcified, the second is taken to be the normal. Normal vector is
1202 | // normalised before storage.
1203 | setVectors: function(anchor, v1, v2) {
1204 | anchor = Vector.create(anchor);
1205 | anchor = anchor.to3D(); if (anchor === null) { return null; }
1206 | v1 = Vector.create(v1);
1207 | v1 = v1.to3D(); if (v1 === null) { return null; }
1208 | if (typeof(v2) == 'undefined') {
1209 | v2 = null;
1210 | } else {
1211 | v2 = Vector.create(v2);
1212 | v2 = v2.to3D(); if (v2 === null) { return null; }
1213 | }
1214 | var A1 = anchor.elements[0], A2 = anchor.elements[1], A3 = anchor.elements[2];
1215 | var v11 = v1.elements[0], v12 = v1.elements[1], v13 = v1.elements[2];
1216 | var normal, mod;
1217 | if (v2 !== null) {
1218 | var v21 = v2.elements[0], v22 = v2.elements[1], v23 = v2.elements[2];
1219 | normal = Vector.create([
1220 | (v12 - A2) * (v23 - A3) - (v13 - A3) * (v22 - A2),
1221 | (v13 - A3) * (v21 - A1) - (v11 - A1) * (v23 - A3),
1222 | (v11 - A1) * (v22 - A2) - (v12 - A2) * (v21 - A1)
1223 | ]);
1224 | mod = normal.modulus();
1225 | if (mod === 0) { return null; }
1226 | normal = Vector.create([normal.elements[0] / mod, normal.elements[1] / mod, normal.elements[2] / mod]);
1227 | } else {
1228 | mod = Math.sqrt(v11*v11 + v12*v12 + v13*v13);
1229 | if (mod === 0) { return null; }
1230 | normal = Vector.create([v1.elements[0] / mod, v1.elements[1] / mod, v1.elements[2] / mod]);
1231 | }
1232 | this.anchor = anchor;
1233 | this.normal = normal;
1234 | return this;
1235 | }
1236 | };
1237 |
1238 | // Constructor function
1239 | Plane.create = function(anchor, v1, v2) {
1240 | var P = new Plane();
1241 | return P.setVectors(anchor, v1, v2);
1242 | };
1243 |
1244 | // X-Y-Z planes
1245 | Plane.XY = Plane.create(Vector.Zero(3), Vector.k);
1246 | Plane.YZ = Plane.create(Vector.Zero(3), Vector.i);
1247 | Plane.ZX = Plane.create(Vector.Zero(3), Vector.j);
1248 | Plane.YX = Plane.XY; Plane.ZY = Plane.YZ; Plane.XZ = Plane.ZX;
1249 |
1250 | // Utility functions
1251 | var $V = Vector.create;
1252 | var $M = Matrix.create;
1253 | var $L = Line.create;
1254 | var $P = Plane.create;
1255 |
--------------------------------------------------------------------------------
/lib/underscore.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.3.3
2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Underscore may be freely distributed under the MIT license.
4 | // Portions of Underscore are inspired or borrowed from Prototype,
5 | // Oliver Steele's Functional, and John Resig's Micro-Templating.
6 | // For all details and documentation:
7 | // http://documentcloud.github.com/underscore
8 |
9 | (function() {
10 |
11 | // Baseline setup
12 | // --------------
13 |
14 | // Establish the root object, `window` in the browser, or `global` on the server.
15 | var root = this;
16 |
17 | // Save the previous value of the `_` variable.
18 | var previousUnderscore = root._;
19 |
20 | // Establish the object that gets returned to break out of a loop iteration.
21 | var breaker = {};
22 |
23 | // Save bytes in the minified (but not gzipped) version:
24 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
25 |
26 | // Create quick reference variables for speed access to core prototypes.
27 | var slice = ArrayProto.slice,
28 | unshift = ArrayProto.unshift,
29 | toString = ObjProto.toString,
30 | hasOwnProperty = ObjProto.hasOwnProperty;
31 |
32 | // All **ECMAScript 5** native function implementations that we hope to use
33 | // are declared here.
34 | var
35 | nativeForEach = ArrayProto.forEach,
36 | nativeMap = ArrayProto.map,
37 | nativeReduce = ArrayProto.reduce,
38 | nativeReduceRight = ArrayProto.reduceRight,
39 | nativeFilter = ArrayProto.filter,
40 | nativeEvery = ArrayProto.every,
41 | nativeSome = ArrayProto.some,
42 | nativeIndexOf = ArrayProto.indexOf,
43 | nativeLastIndexOf = ArrayProto.lastIndexOf,
44 | nativeIsArray = Array.isArray,
45 | nativeKeys = Object.keys,
46 | nativeBind = FuncProto.bind;
47 |
48 | // Create a safe reference to the Underscore object for use below.
49 | var _ = function(obj) { return new wrapper(obj); };
50 |
51 | // Export the Underscore object for **Node.js**, with
52 | // backwards-compatibility for the old `require()` API. If we're in
53 | // the browser, add `_` as a global object via a string identifier,
54 | // for Closure Compiler "advanced" mode.
55 | if (typeof exports !== 'undefined') {
56 | if (typeof module !== 'undefined' && module.exports) {
57 | exports = module.exports = _;
58 | }
59 | exports._ = _;
60 | } else {
61 | root['_'] = _;
62 | }
63 |
64 | // Current version.
65 | _.VERSION = '1.3.3';
66 |
67 | // Collection Functions
68 | // --------------------
69 |
70 | // The cornerstone, an `each` implementation, aka `forEach`.
71 | // Handles objects with the built-in `forEach`, arrays, and raw objects.
72 | // Delegates to **ECMAScript 5**'s native `forEach` if available.
73 | var each = _.each = _.forEach = function(obj, iterator, context) {
74 | if (obj == null) return;
75 | if (nativeForEach && obj.forEach === nativeForEach) {
76 | obj.forEach(iterator, context);
77 | } else if (obj.length === +obj.length) {
78 | for (var i = 0, l = obj.length; i < l; i++) {
79 | if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
80 | }
81 | } else {
82 | for (var key in obj) {
83 | if (_.has(obj, key)) {
84 | if (iterator.call(context, obj[key], key, obj) === breaker) return;
85 | }
86 | }
87 | }
88 | };
89 |
90 | // Return the results of applying the iterator to each element.
91 | // Delegates to **ECMAScript 5**'s native `map` if available.
92 | _.map = _.collect = function(obj, iterator, context) {
93 | var results = [];
94 | if (obj == null) return results;
95 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
96 | each(obj, function(value, index, list) {
97 | results[results.length] = iterator.call(context, value, index, list);
98 | });
99 | if (obj.length === +obj.length) results.length = obj.length;
100 | return results;
101 | };
102 |
103 | // **Reduce** builds up a single result from a list of values, aka `inject`,
104 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
105 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
106 | var initial = arguments.length > 2;
107 | if (obj == null) obj = [];
108 | if (nativeReduce && obj.reduce === nativeReduce) {
109 | if (context) iterator = _.bind(iterator, context);
110 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
111 | }
112 | each(obj, function(value, index, list) {
113 | if (!initial) {
114 | memo = value;
115 | initial = true;
116 | } else {
117 | memo = iterator.call(context, memo, value, index, list);
118 | }
119 | });
120 | if (!initial) throw new TypeError('Reduce of empty array with no initial value');
121 | return memo;
122 | };
123 |
124 | // The right-associative version of reduce, also known as `foldr`.
125 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
126 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
127 | var initial = arguments.length > 2;
128 | if (obj == null) obj = [];
129 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
130 | if (context) iterator = _.bind(iterator, context);
131 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
132 | }
133 | var reversed = _.toArray(obj).reverse();
134 | if (context && !initial) iterator = _.bind(iterator, context);
135 | return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
136 | };
137 |
138 | // Return the first value which passes a truth test. Aliased as `detect`.
139 | _.find = _.detect = function(obj, iterator, context) {
140 | var result;
141 | any(obj, function(value, index, list) {
142 | if (iterator.call(context, value, index, list)) {
143 | result = value;
144 | return true;
145 | }
146 | });
147 | return result;
148 | };
149 |
150 | // Return all the elements that pass a truth test.
151 | // Delegates to **ECMAScript 5**'s native `filter` if available.
152 | // Aliased as `select`.
153 | _.filter = _.select = function(obj, iterator, context) {
154 | var results = [];
155 | if (obj == null) return results;
156 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
157 | each(obj, function(value, index, list) {
158 | if (iterator.call(context, value, index, list)) results[results.length] = value;
159 | });
160 | return results;
161 | };
162 |
163 | // Return all the elements for which a truth test fails.
164 | _.reject = function(obj, iterator, context) {
165 | var results = [];
166 | if (obj == null) return results;
167 | each(obj, function(value, index, list) {
168 | if (!iterator.call(context, value, index, list)) results[results.length] = value;
169 | });
170 | return results;
171 | };
172 |
173 | // Determine whether all of the elements match a truth test.
174 | // Delegates to **ECMAScript 5**'s native `every` if available.
175 | // Aliased as `all`.
176 | _.every = _.all = function(obj, iterator, context) {
177 | var result = true;
178 | if (obj == null) return result;
179 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
180 | each(obj, function(value, index, list) {
181 | if (!(result = result && iterator.call(context, value, index, list))) return breaker;
182 | });
183 | return !!result;
184 | };
185 |
186 | // Determine if at least one element in the object matches a truth test.
187 | // Delegates to **ECMAScript 5**'s native `some` if available.
188 | // Aliased as `any`.
189 | var any = _.some = _.any = function(obj, iterator, context) {
190 | iterator || (iterator = _.identity);
191 | var result = false;
192 | if (obj == null) return result;
193 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
194 | each(obj, function(value, index, list) {
195 | if (result || (result = iterator.call(context, value, index, list))) return breaker;
196 | });
197 | return !!result;
198 | };
199 |
200 | // Determine if a given value is included in the array or object using `===`.
201 | // Aliased as `contains`.
202 | _.include = _.contains = function(obj, target) {
203 | var found = false;
204 | if (obj == null) return found;
205 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
206 | found = any(obj, function(value) {
207 | return value === target;
208 | });
209 | return found;
210 | };
211 |
212 | // Invoke a method (with arguments) on every item in a collection.
213 | _.invoke = function(obj, method) {
214 | var args = slice.call(arguments, 2);
215 | return _.map(obj, function(value) {
216 | return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
217 | });
218 | };
219 |
220 | // Convenience version of a common use case of `map`: fetching a property.
221 | _.pluck = function(obj, key) {
222 | return _.map(obj, function(value){ return value[key]; });
223 | };
224 |
225 | // Return the maximum element or (element-based computation).
226 | _.max = function(obj, iterator, context) {
227 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj);
228 | if (!iterator && _.isEmpty(obj)) return -Infinity;
229 | var result = {computed : -Infinity};
230 | each(obj, function(value, index, list) {
231 | var computed = iterator ? iterator.call(context, value, index, list) : value;
232 | computed >= result.computed && (result = {value : value, computed : computed});
233 | });
234 | return result.value;
235 | };
236 |
237 | // Return the minimum element (or element-based computation).
238 | _.min = function(obj, iterator, context) {
239 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj);
240 | if (!iterator && _.isEmpty(obj)) return Infinity;
241 | var result = {computed : Infinity};
242 | each(obj, function(value, index, list) {
243 | var computed = iterator ? iterator.call(context, value, index, list) : value;
244 | computed < result.computed && (result = {value : value, computed : computed});
245 | });
246 | return result.value;
247 | };
248 |
249 | // Shuffle an array.
250 | _.shuffle = function(obj) {
251 | var shuffled = [], rand;
252 | each(obj, function(value, index, list) {
253 | rand = Math.floor(Math.random() * (index + 1));
254 | shuffled[index] = shuffled[rand];
255 | shuffled[rand] = value;
256 | });
257 | return shuffled;
258 | };
259 |
260 | // Sort the object's values by a criterion produced by an iterator.
261 | _.sortBy = function(obj, val, context) {
262 | var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
263 | return _.pluck(_.map(obj, function(value, index, list) {
264 | return {
265 | value : value,
266 | criteria : iterator.call(context, value, index, list)
267 | };
268 | }).sort(function(left, right) {
269 | var a = left.criteria, b = right.criteria;
270 | if (a === void 0) return 1;
271 | if (b === void 0) return -1;
272 | return a < b ? -1 : a > b ? 1 : 0;
273 | }), 'value');
274 | };
275 |
276 | // Groups the object's values by a criterion. Pass either a string attribute
277 | // to group by, or a function that returns the criterion.
278 | _.groupBy = function(obj, val) {
279 | var result = {};
280 | var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
281 | each(obj, function(value, index) {
282 | var key = iterator(value, index);
283 | (result[key] || (result[key] = [])).push(value);
284 | });
285 | return result;
286 | };
287 |
288 | // Use a comparator function to figure out at what index an object should
289 | // be inserted so as to maintain order. Uses binary search.
290 | _.sortedIndex = function(array, obj, iterator) {
291 | iterator || (iterator = _.identity);
292 | var low = 0, high = array.length;
293 | while (low < high) {
294 | var mid = (low + high) >> 1;
295 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
296 | }
297 | return low;
298 | };
299 |
300 | // Safely convert anything iterable into a real, live array.
301 | _.toArray = function(obj) {
302 | if (!obj) return [];
303 | if (_.isArray(obj)) return slice.call(obj);
304 | if (_.isArguments(obj)) return slice.call(obj);
305 | if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
306 | return _.values(obj);
307 | };
308 |
309 | // Return the number of elements in an object.
310 | _.size = function(obj) {
311 | return _.isArray(obj) ? obj.length : _.keys(obj).length;
312 | };
313 |
314 | // Array Functions
315 | // ---------------
316 |
317 | // Get the first element of an array. Passing **n** will return the first N
318 | // values in the array. Aliased as `head` and `take`. The **guard** check
319 | // allows it to work with `_.map`.
320 | _.first = _.head = _.take = function(array, n, guard) {
321 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
322 | };
323 |
324 | // Returns everything but the last entry of the array. Especcialy useful on
325 | // the arguments object. Passing **n** will return all the values in
326 | // the array, excluding the last N. The **guard** check allows it to work with
327 | // `_.map`.
328 | _.initial = function(array, n, guard) {
329 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
330 | };
331 |
332 | // Get the last element of an array. Passing **n** will return the last N
333 | // values in the array. The **guard** check allows it to work with `_.map`.
334 | _.last = function(array, n, guard) {
335 | if ((n != null) && !guard) {
336 | return slice.call(array, Math.max(array.length - n, 0));
337 | } else {
338 | return array[array.length - 1];
339 | }
340 | };
341 |
342 | // Returns everything but the first entry of the array. Aliased as `tail`.
343 | // Especially useful on the arguments object. Passing an **index** will return
344 | // the rest of the values in the array from that index onward. The **guard**
345 | // check allows it to work with `_.map`.
346 | _.rest = _.tail = function(array, index, guard) {
347 | return slice.call(array, (index == null) || guard ? 1 : index);
348 | };
349 |
350 | // Trim out all falsy values from an array.
351 | _.compact = function(array) {
352 | return _.filter(array, function(value){ return !!value; });
353 | };
354 |
355 | // Return a completely flattened version of an array.
356 | _.flatten = function(array, shallow) {
357 | return _.reduce(array, function(memo, value) {
358 | if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
359 | memo[memo.length] = value;
360 | return memo;
361 | }, []);
362 | };
363 |
364 | // Return a version of the array that does not contain the specified value(s).
365 | _.without = function(array) {
366 | return _.difference(array, slice.call(arguments, 1));
367 | };
368 |
369 | // Produce a duplicate-free version of the array. If the array has already
370 | // been sorted, you have the option of using a faster algorithm.
371 | // Aliased as `unique`.
372 | _.uniq = _.unique = function(array, isSorted, iterator) {
373 | var initial = iterator ? _.map(array, iterator) : array;
374 | var results = [];
375 | // The `isSorted` flag is irrelevant if the array only contains two elements.
376 | if (array.length < 3) isSorted = true;
377 | _.reduce(initial, function (memo, value, index) {
378 | if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
379 | memo.push(value);
380 | results.push(array[index]);
381 | }
382 | return memo;
383 | }, []);
384 | return results;
385 | };
386 |
387 | // Produce an array that contains the union: each distinct element from all of
388 | // the passed-in arrays.
389 | _.union = function() {
390 | return _.uniq(_.flatten(arguments, true));
391 | };
392 |
393 | // Produce an array that contains every item shared between all the
394 | // passed-in arrays. (Aliased as "intersect" for back-compat.)
395 | _.intersection = _.intersect = function(array) {
396 | var rest = slice.call(arguments, 1);
397 | return _.filter(_.uniq(array), function(item) {
398 | return _.every(rest, function(other) {
399 | return _.indexOf(other, item) >= 0;
400 | });
401 | });
402 | };
403 |
404 | // Take the difference between one array and a number of other arrays.
405 | // Only the elements present in just the first array will remain.
406 | _.difference = function(array) {
407 | var rest = _.flatten(slice.call(arguments, 1), true);
408 | return _.filter(array, function(value){ return !_.include(rest, value); });
409 | };
410 |
411 | // Zip together multiple lists into a single array -- elements that share
412 | // an index go together.
413 | _.zip = function() {
414 | var args = slice.call(arguments);
415 | var length = _.max(_.pluck(args, 'length'));
416 | var results = new Array(length);
417 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
418 | return results;
419 | };
420 |
421 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
422 | // we need this function. Return the position of the first occurrence of an
423 | // item in an array, or -1 if the item is not included in the array.
424 | // Delegates to **ECMAScript 5**'s native `indexOf` if available.
425 | // If the array is large and already in sort order, pass `true`
426 | // for **isSorted** to use binary search.
427 | _.indexOf = function(array, item, isSorted) {
428 | if (array == null) return -1;
429 | var i, l;
430 | if (isSorted) {
431 | i = _.sortedIndex(array, item);
432 | return array[i] === item ? i : -1;
433 | }
434 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
435 | for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
436 | return -1;
437 | };
438 |
439 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
440 | _.lastIndexOf = function(array, item) {
441 | if (array == null) return -1;
442 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
443 | var i = array.length;
444 | while (i--) if (i in array && array[i] === item) return i;
445 | return -1;
446 | };
447 |
448 | // Generate an integer Array containing an arithmetic progression. A port of
449 | // the native Python `range()` function. See
450 | // [the Python documentation](http://docs.python.org/library/functions.html#range).
451 | _.range = function(start, stop, step) {
452 | if (arguments.length <= 1) {
453 | stop = start || 0;
454 | start = 0;
455 | }
456 | step = arguments[2] || 1;
457 |
458 | var len = Math.max(Math.ceil((stop - start) / step), 0);
459 | var idx = 0;
460 | var range = new Array(len);
461 |
462 | while(idx < len) {
463 | range[idx++] = start;
464 | start += step;
465 | }
466 |
467 | return range;
468 | };
469 |
470 | // Function (ahem) Functions
471 | // ------------------
472 |
473 | // Reusable constructor function for prototype setting.
474 | var ctor = function(){};
475 |
476 | // Create a function bound to a given object (assigning `this`, and arguments,
477 | // optionally). Binding with arguments is also known as `curry`.
478 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
479 | // We check for `func.bind` first, to fail fast when `func` is undefined.
480 | _.bind = function bind(func, context) {
481 | var bound, args;
482 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
483 | if (!_.isFunction(func)) throw new TypeError;
484 | args = slice.call(arguments, 2);
485 | return bound = function() {
486 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
487 | ctor.prototype = func.prototype;
488 | var self = new ctor;
489 | var result = func.apply(self, args.concat(slice.call(arguments)));
490 | if (Object(result) === result) return result;
491 | return self;
492 | };
493 | };
494 |
495 | // Bind all of an object's methods to that object. Useful for ensuring that
496 | // all callbacks defined on an object belong to it.
497 | _.bindAll = function(obj) {
498 | var funcs = slice.call(arguments, 1);
499 | if (funcs.length == 0) funcs = _.functions(obj);
500 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
501 | return obj;
502 | };
503 |
504 | // Memoize an expensive function by storing its results.
505 | _.memoize = function(func, hasher) {
506 | var memo = {};
507 | hasher || (hasher = _.identity);
508 | return function() {
509 | var key = hasher.apply(this, arguments);
510 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
511 | };
512 | };
513 |
514 | // Delays a function for the given number of milliseconds, and then calls
515 | // it with the arguments supplied.
516 | _.delay = function(func, wait) {
517 | var args = slice.call(arguments, 2);
518 | return setTimeout(function(){ return func.apply(null, args); }, wait);
519 | };
520 |
521 | // Defers a function, scheduling it to run after the current call stack has
522 | // cleared.
523 | _.defer = function(func) {
524 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
525 | };
526 |
527 | // Returns a function, that, when invoked, will only be triggered at most once
528 | // during a given window of time.
529 | _.throttle = function(func, wait) {
530 | var context, args, timeout, throttling, more, result;
531 | var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
532 | return function() {
533 | context = this; args = arguments;
534 | var later = function() {
535 | timeout = null;
536 | if (more) func.apply(context, args);
537 | whenDone();
538 | };
539 | if (!timeout) timeout = setTimeout(later, wait);
540 | if (throttling) {
541 | more = true;
542 | } else {
543 | result = func.apply(context, args);
544 | }
545 | whenDone();
546 | throttling = true;
547 | return result;
548 | };
549 | };
550 |
551 | // Returns a function, that, as long as it continues to be invoked, will not
552 | // be triggered. The function will be called after it stops being called for
553 | // N milliseconds. If `immediate` is passed, trigger the function on the
554 | // leading edge, instead of the trailing.
555 | _.debounce = function(func, wait, immediate) {
556 | var timeout;
557 | return function() {
558 | var context = this, args = arguments;
559 | var later = function() {
560 | timeout = null;
561 | if (!immediate) func.apply(context, args);
562 | };
563 | if (immediate && !timeout) func.apply(context, args);
564 | clearTimeout(timeout);
565 | timeout = setTimeout(later, wait);
566 | };
567 | };
568 |
569 | // Returns a function that will be executed at most one time, no matter how
570 | // often you call it. Useful for lazy initialization.
571 | _.once = function(func) {
572 | var ran = false, memo;
573 | return function() {
574 | if (ran) return memo;
575 | ran = true;
576 | return memo = func.apply(this, arguments);
577 | };
578 | };
579 |
580 | // Returns the first function passed as an argument to the second,
581 | // allowing you to adjust arguments, run code before and after, and
582 | // conditionally execute the original function.
583 | _.wrap = function(func, wrapper) {
584 | return function() {
585 | var args = [func].concat(slice.call(arguments, 0));
586 | return wrapper.apply(this, args);
587 | };
588 | };
589 |
590 | // Returns a function that is the composition of a list of functions, each
591 | // consuming the return value of the function that follows.
592 | _.compose = function() {
593 | var funcs = arguments;
594 | return function() {
595 | var args = arguments;
596 | for (var i = funcs.length - 1; i >= 0; i--) {
597 | args = [funcs[i].apply(this, args)];
598 | }
599 | return args[0];
600 | };
601 | };
602 |
603 | // Returns a function that will only be executed after being called N times.
604 | _.after = function(times, func) {
605 | if (times <= 0) return func();
606 | return function() {
607 | if (--times < 1) { return func.apply(this, arguments); }
608 | };
609 | };
610 |
611 | // Object Functions
612 | // ----------------
613 |
614 | // Retrieve the names of an object's properties.
615 | // Delegates to **ECMAScript 5**'s native `Object.keys`
616 | _.keys = nativeKeys || function(obj) {
617 | if (obj !== Object(obj)) throw new TypeError('Invalid object');
618 | var keys = [];
619 | for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
620 | return keys;
621 | };
622 |
623 | // Retrieve the values of an object's properties.
624 | _.values = function(obj) {
625 | return _.map(obj, _.identity);
626 | };
627 |
628 | // Return a sorted list of the function names available on the object.
629 | // Aliased as `methods`
630 | _.functions = _.methods = function(obj) {
631 | var names = [];
632 | for (var key in obj) {
633 | if (_.isFunction(obj[key])) names.push(key);
634 | }
635 | return names.sort();
636 | };
637 |
638 | // Extend a given object with all the properties in passed-in object(s).
639 | _.extend = function(obj) {
640 | each(slice.call(arguments, 1), function(source) {
641 | for (var prop in source) {
642 | obj[prop] = source[prop];
643 | }
644 | });
645 | return obj;
646 | };
647 |
648 | // Return a copy of the object only containing the whitelisted properties.
649 | _.pick = function(obj) {
650 | var result = {};
651 | each(_.flatten(slice.call(arguments, 1)), function(key) {
652 | if (key in obj) result[key] = obj[key];
653 | });
654 | return result;
655 | };
656 |
657 | // Fill in a given object with default properties.
658 | _.defaults = function(obj) {
659 | each(slice.call(arguments, 1), function(source) {
660 | for (var prop in source) {
661 | if (obj[prop] == null) obj[prop] = source[prop];
662 | }
663 | });
664 | return obj;
665 | };
666 |
667 | // Create a (shallow-cloned) duplicate of an object.
668 | _.clone = function(obj) {
669 | if (!_.isObject(obj)) return obj;
670 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
671 | };
672 |
673 | // Invokes interceptor with the obj, and then returns obj.
674 | // The primary purpose of this method is to "tap into" a method chain, in
675 | // order to perform operations on intermediate results within the chain.
676 | _.tap = function(obj, interceptor) {
677 | interceptor(obj);
678 | return obj;
679 | };
680 |
681 | // Internal recursive comparison function.
682 | function eq(a, b, stack) {
683 | // Identical objects are equal. `0 === -0`, but they aren't identical.
684 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
685 | if (a === b) return a !== 0 || 1 / a == 1 / b;
686 | // A strict comparison is necessary because `null == undefined`.
687 | if (a == null || b == null) return a === b;
688 | // Unwrap any wrapped objects.
689 | if (a._chain) a = a._wrapped;
690 | if (b._chain) b = b._wrapped;
691 | // Invoke a custom `isEqual` method if one is provided.
692 | if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
693 | if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
694 | // Compare `[[Class]]` names.
695 | var className = toString.call(a);
696 | if (className != toString.call(b)) return false;
697 | switch (className) {
698 | // Strings, numbers, dates, and booleans are compared by value.
699 | case '[object String]':
700 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
701 | // equivalent to `new String("5")`.
702 | return a == String(b);
703 | case '[object Number]':
704 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
705 | // other numeric values.
706 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
707 | case '[object Date]':
708 | case '[object Boolean]':
709 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their
710 | // millisecond representations. Note that invalid dates with millisecond representations
711 | // of `NaN` are not equivalent.
712 | return +a == +b;
713 | // RegExps are compared by their source patterns and flags.
714 | case '[object RegExp]':
715 | return a.source == b.source &&
716 | a.global == b.global &&
717 | a.multiline == b.multiline &&
718 | a.ignoreCase == b.ignoreCase;
719 | }
720 | if (typeof a != 'object' || typeof b != 'object') return false;
721 | // Assume equality for cyclic structures. The algorithm for detecting cyclic
722 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
723 | var length = stack.length;
724 | while (length--) {
725 | // Linear search. Performance is inversely proportional to the number of
726 | // unique nested structures.
727 | if (stack[length] == a) return true;
728 | }
729 | // Add the first object to the stack of traversed objects.
730 | stack.push(a);
731 | var size = 0, result = true;
732 | // Recursively compare objects and arrays.
733 | if (className == '[object Array]') {
734 | // Compare array lengths to determine if a deep comparison is necessary.
735 | size = a.length;
736 | result = size == b.length;
737 | if (result) {
738 | // Deep compare the contents, ignoring non-numeric properties.
739 | while (size--) {
740 | // Ensure commutative equality for sparse arrays.
741 | if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
742 | }
743 | }
744 | } else {
745 | // Objects with different constructors are not equivalent.
746 | if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
747 | // Deep compare objects.
748 | for (var key in a) {
749 | if (_.has(a, key)) {
750 | // Count the expected number of properties.
751 | size++;
752 | // Deep compare each member.
753 | if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
754 | }
755 | }
756 | // Ensure that both objects contain the same number of properties.
757 | if (result) {
758 | for (key in b) {
759 | if (_.has(b, key) && !(size--)) break;
760 | }
761 | result = !size;
762 | }
763 | }
764 | // Remove the first object from the stack of traversed objects.
765 | stack.pop();
766 | return result;
767 | }
768 |
769 | // Perform a deep comparison to check if two objects are equal.
770 | _.isEqual = function(a, b) {
771 | return eq(a, b, []);
772 | };
773 |
774 | // Is a given array, string, or object empty?
775 | // An "empty" object has no enumerable own-properties.
776 | _.isEmpty = function(obj) {
777 | if (obj == null) return true;
778 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
779 | for (var key in obj) if (_.has(obj, key)) return false;
780 | return true;
781 | };
782 |
783 | // Is a given value a DOM element?
784 | _.isElement = function(obj) {
785 | return !!(obj && obj.nodeType == 1);
786 | };
787 |
788 | // Is a given value an array?
789 | // Delegates to ECMA5's native Array.isArray
790 | _.isArray = nativeIsArray || function(obj) {
791 | return toString.call(obj) == '[object Array]';
792 | };
793 |
794 | // Is a given variable an object?
795 | _.isObject = function(obj) {
796 | return obj === Object(obj);
797 | };
798 |
799 | // Is a given variable an arguments object?
800 | _.isArguments = function(obj) {
801 | return toString.call(obj) == '[object Arguments]';
802 | };
803 | if (!_.isArguments(arguments)) {
804 | _.isArguments = function(obj) {
805 | return !!(obj && _.has(obj, 'callee'));
806 | };
807 | }
808 |
809 | // Is a given value a function?
810 | _.isFunction = function(obj) {
811 | return toString.call(obj) == '[object Function]';
812 | };
813 |
814 | // Is a given value a string?
815 | _.isString = function(obj) {
816 | return toString.call(obj) == '[object String]';
817 | };
818 |
819 | // Is a given value a number?
820 | _.isNumber = function(obj) {
821 | return toString.call(obj) == '[object Number]';
822 | };
823 |
824 | // Is a given object a finite number?
825 | _.isFinite = function(obj) {
826 | return _.isNumber(obj) && isFinite(obj);
827 | };
828 |
829 | // Is the given value `NaN`?
830 | _.isNaN = function(obj) {
831 | // `NaN` is the only value for which `===` is not reflexive.
832 | return obj !== obj;
833 | };
834 |
835 | // Is a given value a boolean?
836 | _.isBoolean = function(obj) {
837 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
838 | };
839 |
840 | // Is a given value a date?
841 | _.isDate = function(obj) {
842 | return toString.call(obj) == '[object Date]';
843 | };
844 |
845 | // Is the given value a regular expression?
846 | _.isRegExp = function(obj) {
847 | return toString.call(obj) == '[object RegExp]';
848 | };
849 |
850 | // Is a given value equal to null?
851 | _.isNull = function(obj) {
852 | return obj === null;
853 | };
854 |
855 | // Is a given variable undefined?
856 | _.isUndefined = function(obj) {
857 | return obj === void 0;
858 | };
859 |
860 | // Has own property?
861 | _.has = function(obj, key) {
862 | return hasOwnProperty.call(obj, key);
863 | };
864 |
865 | // Utility Functions
866 | // -----------------
867 |
868 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
869 | // previous owner. Returns a reference to the Underscore object.
870 | _.noConflict = function() {
871 | root._ = previousUnderscore;
872 | return this;
873 | };
874 |
875 | // Keep the identity function around for default iterators.
876 | _.identity = function(value) {
877 | return value;
878 | };
879 |
880 | // Run a function **n** times.
881 | _.times = function (n, iterator, context) {
882 | for (var i = 0; i < n; i++) iterator.call(context, i);
883 | };
884 |
885 | // Escape a string for HTML interpolation.
886 | _.escape = function(string) {
887 | return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
888 | };
889 |
890 | // If the value of the named property is a function then invoke it;
891 | // otherwise, return it.
892 | _.result = function(object, property) {
893 | if (object == null) return null;
894 | var value = object[property];
895 | return _.isFunction(value) ? value.call(object) : value;
896 | };
897 |
898 | // Add your own custom functions to the Underscore object, ensuring that
899 | // they're correctly added to the OOP wrapper as well.
900 | _.mixin = function(obj) {
901 | each(_.functions(obj), function(name){
902 | addToWrapper(name, _[name] = obj[name]);
903 | });
904 | };
905 |
906 | // Generate a unique integer id (unique within the entire client session).
907 | // Useful for temporary DOM ids.
908 | var idCounter = 0;
909 | _.uniqueId = function(prefix) {
910 | var id = idCounter++;
911 | return prefix ? prefix + id : id;
912 | };
913 |
914 | // By default, Underscore uses ERB-style template delimiters, change the
915 | // following template settings to use alternative delimiters.
916 | _.templateSettings = {
917 | evaluate : /<%([\s\S]+?)%>/g,
918 | interpolate : /<%=([\s\S]+?)%>/g,
919 | escape : /<%-([\s\S]+?)%>/g
920 | };
921 |
922 | // When customizing `templateSettings`, if you don't want to define an
923 | // interpolation, evaluation or escaping regex, we need one that is
924 | // guaranteed not to match.
925 | var noMatch = /.^/;
926 |
927 | // Certain characters need to be escaped so that they can be put into a
928 | // string literal.
929 | var escapes = {
930 | '\\': '\\',
931 | "'": "'",
932 | 'r': '\r',
933 | 'n': '\n',
934 | 't': '\t',
935 | 'u2028': '\u2028',
936 | 'u2029': '\u2029'
937 | };
938 |
939 | for (var p in escapes) escapes[escapes[p]] = p;
940 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
941 | var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
942 |
943 | // Within an interpolation, evaluation, or escaping, remove HTML escaping
944 | // that had been previously added.
945 | var unescape = function(code) {
946 | return code.replace(unescaper, function(match, escape) {
947 | return escapes[escape];
948 | });
949 | };
950 |
951 | // JavaScript micro-templating, similar to John Resig's implementation.
952 | // Underscore templating handles arbitrary delimiters, preserves whitespace,
953 | // and correctly escapes quotes within interpolated code.
954 | _.template = function(text, data, settings) {
955 | settings = _.defaults(settings || {}, _.templateSettings);
956 |
957 | // Compile the template source, taking care to escape characters that
958 | // cannot be included in a string literal and then unescape them in code
959 | // blocks.
960 | var source = "__p+='" + text
961 | .replace(escaper, function(match) {
962 | return '\\' + escapes[match];
963 | })
964 | .replace(settings.escape || noMatch, function(match, code) {
965 | return "'+\n_.escape(" + unescape(code) + ")+\n'";
966 | })
967 | .replace(settings.interpolate || noMatch, function(match, code) {
968 | return "'+\n(" + unescape(code) + ")+\n'";
969 | })
970 | .replace(settings.evaluate || noMatch, function(match, code) {
971 | return "';\n" + unescape(code) + "\n;__p+='";
972 | }) + "';\n";
973 |
974 | // If a variable is not specified, place data values in local scope.
975 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
976 |
977 | source = "var __p='';" +
978 | "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" +
979 | source + "return __p;\n";
980 |
981 | var render = new Function(settings.variable || 'obj', '_', source);
982 | if (data) return render(data, _);
983 | var template = function(data) {
984 | return render.call(this, data, _);
985 | };
986 |
987 | // Provide the compiled function source as a convenience for build time
988 | // precompilation.
989 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' +
990 | source + '}';
991 |
992 | return template;
993 | };
994 |
995 | // Add a "chain" function, which will delegate to the wrapper.
996 | _.chain = function(obj) {
997 | return _(obj).chain();
998 | };
999 |
1000 | // The OOP Wrapper
1001 | // ---------------
1002 |
1003 | // If Underscore is called as a function, it returns a wrapped object that
1004 | // can be used OO-style. This wrapper holds altered versions of all the
1005 | // underscore functions. Wrapped objects may be chained.
1006 | var wrapper = function(obj) { this._wrapped = obj; };
1007 |
1008 | // Expose `wrapper.prototype` as `_.prototype`
1009 | _.prototype = wrapper.prototype;
1010 |
1011 | // Helper function to continue chaining intermediate results.
1012 | var result = function(obj, chain) {
1013 | return chain ? _(obj).chain() : obj;
1014 | };
1015 |
1016 | // A method to easily add functions to the OOP wrapper.
1017 | var addToWrapper = function(name, func) {
1018 | wrapper.prototype[name] = function() {
1019 | var args = slice.call(arguments);
1020 | unshift.call(args, this._wrapped);
1021 | return result(func.apply(_, args), this._chain);
1022 | };
1023 | };
1024 |
1025 | // Add all of the Underscore functions to the wrapper object.
1026 | _.mixin(_);
1027 |
1028 | // Add all mutator Array functions to the wrapper.
1029 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1030 | var method = ArrayProto[name];
1031 | wrapper.prototype[name] = function() {
1032 | var wrapped = this._wrapped;
1033 | method.apply(wrapped, arguments);
1034 | var length = wrapped.length;
1035 | if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
1036 | return result(wrapped, this._chain);
1037 | };
1038 | });
1039 |
1040 | // Add all accessor Array functions to the wrapper.
1041 | each(['concat', 'join', 'slice'], function(name) {
1042 | var method = ArrayProto[name];
1043 | wrapper.prototype[name] = function() {
1044 | return result(method.apply(this._wrapped, arguments), this._chain);
1045 | };
1046 | });
1047 |
1048 | // Start chaining a wrapped Underscore object.
1049 | wrapper.prototype.chain = function() {
1050 | this._chain = true;
1051 | return this;
1052 | };
1053 |
1054 | // Extracts the result from a wrapped and chained object.
1055 | wrapper.prototype.value = function() {
1056 | return this._wrapped;
1057 | };
1058 |
1059 | }).call(this);
1060 |
--------------------------------------------------------------------------------
/spreadsheet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
23 | |
24 | {{column}} |
25 |
26 |
27 | {{row}} |
28 |
29 |
34 | |
35 |
36 |
37 |
38 |
39 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/core.coffee:
--------------------------------------------------------------------------------
1 | @sheet = ($parse, $scope) ->
2 | $scope.rows = _.map(_.range(10), -> [""])
3 | $scope.compute = (rowIndex) ->
4 | $parse($scope.rows[rowIndex][0])($scope)
5 |
--------------------------------------------------------------------------------