├── 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 |
18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 |
{{compute($index)}}
26 |
27 |
28 | 29 |
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 | 25 | 26 | 27 | 28 | 35 | 36 |
    {{column}}
    {{row}} 29 |
    30 | 31 |
    33 |
    34 |
    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 | --------------------------------------------------------------------------------