├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── css └── style.css ├── index.html ├── js ├── keys.js ├── libs │ └── vue.js ├── main.js └── store.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | 6 | 7 | concat: { 8 | css: { 9 | src: [ 10 | 'css_build/*' 11 | ], 12 | dest: 'build/css/all.css' 13 | } 14 | }, 15 | 16 | cssmin: { 17 | css: { 18 | src: 'build/css/all.css', 19 | dest: 'css/all.min.css' 20 | } 21 | }, 22 | 23 | watch: { 24 | options: { //35729 25 | livereload: true, 26 | }, 27 | css: { 28 | files: ['css/*.css'] 29 | }, 30 | html: { 31 | files: ['*.html'] 32 | }, 33 | js: { 34 | files: ['js/*.js'], 35 | } 36 | }, 37 | 38 | connect: { 39 | 'static': { 40 | options: { 41 | hostname: 'localhost', 42 | port: 8001 43 | // base: 'www-root' 44 | } 45 | } 46 | } 47 | 48 | }); 49 | 50 | grunt.loadNpmTasks('grunt-contrib-concat'); 51 | grunt.loadNpmTasks('grunt-contrib-uglify'); 52 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 53 | grunt.loadNpmTasks('grunt-contrib-watch'); 54 | grunt.loadNpmTasks('grunt-contrib-connect'); 55 | 56 | grunt.registerTask('default', ['concat', 'cssmin', 'browserify', 'uglify']); 57 | grunt.registerTask('server', ['connect:static', 'watch']); 58 | 59 | // define an alias for common tasks 60 | // grunt.registerTask('myTasks', ['task1', 'task2:target', 'task3']); 61 | 62 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Achilleas Kiritsakas(akiritsakas@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vue2048 2 | ======= 3 | 4 | 5 | The 2048 game in Vue.js 6 | 7 | 8 | This is based on 2048 by [Gabriele Cirulli], 9 | and it uses some code from [his repos] about the keybinding, touch events, and the css styling. 10 | 11 | 12 | The main functionality is written using Vue.js, 13 | a very flexible MVVM library. 14 | 15 | 16 | This is inspired from the following [ng-newsletter article] 17 | 18 | 19 | You can play the [game here]. 20 | 21 | 22 | 23 | [Gabriele Cirulli]:http://gabrielecirulli.com/ 24 | [his repos]:https://github.com/gabrielecirulli/2048 25 | [game here]:http://axilleasiv.github.io/vue2048/ 26 | [ng-newsletter article]:http://www.ng-newsletter.com/posts/building-2048-in-angularjs.html 27 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | /*@import url(fonts/clear-sans.css);*/ 2 | html, body { 3 | margin: 0; 4 | padding: 0; 5 | background: #faf8ef; 6 | color: #776e65; 7 | font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif; 8 | font-size: 18px; } 9 | 10 | body { 11 | margin: 80px 0; } 12 | 13 | .heading:after { 14 | content: ""; 15 | display: block; 16 | clear: both; } 17 | 18 | h1.title { 19 | font-size: 80px; 20 | font-weight: bold; 21 | margin: 0; 22 | display: block; 23 | float: left; } 24 | 25 | @-webkit-keyframes move-up { 26 | 0% { 27 | top: 25px; 28 | opacity: 1; } 29 | 30 | 100% { 31 | top: -50px; 32 | opacity: 0; } } 33 | @-moz-keyframes move-up { 34 | 0% { 35 | top: 25px; 36 | opacity: 1; } 37 | 38 | 100% { 39 | top: -50px; 40 | opacity: 0; } } 41 | @keyframes move-up { 42 | 0% { 43 | top: 25px; 44 | opacity: 1; } 45 | 46 | 100% { 47 | top: -50px; 48 | opacity: 0; } } 49 | .scores-container { 50 | float: right; 51 | text-align: right; } 52 | 53 | .score-container, .best-container { 54 | position: relative; 55 | display: inline-block; 56 | background: #bbada0; 57 | padding: 15px 25px; 58 | font-size: 25px; 59 | height: 25px; 60 | line-height: 47px; 61 | font-weight: bold; 62 | border-radius: 3px; 63 | color: white; 64 | margin-top: 8px; 65 | text-align: center; } 66 | .score-container:after, .best-container:after { 67 | position: absolute; 68 | width: 100%; 69 | top: 10px; 70 | left: 0; 71 | text-transform: uppercase; 72 | font-size: 13px; 73 | line-height: 13px; 74 | text-align: center; 75 | color: #eee4da; } 76 | .score-container .score-addition, .best-container .score-addition { 77 | position: absolute; 78 | right: 30px; 79 | color: red; 80 | font-size: 25px; 81 | line-height: 25px; 82 | font-weight: bold; 83 | color: rgba(119, 110, 101, 0.9); 84 | z-index: 100; 85 | -webkit-animation: move-up 600ms ease-in; 86 | -moz-animation: move-up 600ms ease-in; 87 | animation: move-up 600ms ease-in; 88 | -webkit-animation-fill-mode: both; 89 | -moz-animation-fill-mode: both; 90 | animation-fill-mode: both; } 91 | 92 | .score-container:after { 93 | content: "Score"; } 94 | 95 | .best-container:after { 96 | content: "Best"; } 97 | 98 | p { 99 | margin-top: 0; 100 | margin-bottom: 10px; 101 | line-height: 1.65; } 102 | 103 | a { 104 | color: #776e65; 105 | font-weight: bold; 106 | text-decoration: underline; 107 | cursor: pointer; } 108 | 109 | strong.important { 110 | text-transform: uppercase; } 111 | 112 | hr { 113 | border: none; 114 | border-bottom: 1px solid #d8d4d0; 115 | margin-top: 20px; 116 | margin-bottom: 30px; } 117 | 118 | .container { 119 | width: 500px; 120 | margin: 0 auto; } 121 | 122 | @-webkit-keyframes fade-in { 123 | 0% { 124 | opacity: 0; } 125 | 126 | 100% { 127 | opacity: 1; } } 128 | @-moz-keyframes fade-in { 129 | 0% { 130 | opacity: 0; } 131 | 132 | 100% { 133 | opacity: 1; } } 134 | @keyframes fade-in { 135 | 0% { 136 | opacity: 0; } 137 | 138 | 100% { 139 | opacity: 1; } } 140 | .game-container { 141 | margin-top: 40px; 142 | position: relative; 143 | padding: 15px; 144 | cursor: default; 145 | -webkit-touch-callout: none; 146 | -ms-touch-callout: none; 147 | -webkit-user-select: none; 148 | -moz-user-select: none; 149 | -ms-user-select: none; 150 | -ms-touch-action: none; 151 | touch-action: none; 152 | background: #bbada0; 153 | border-radius: 6px; 154 | width: 500px; 155 | height: 500px; 156 | -webkit-box-sizing: border-box; 157 | -moz-box-sizing: border-box; 158 | box-sizing: border-box; } 159 | .game-container .game-message { 160 | display: none; 161 | position: absolute; 162 | top: 0; 163 | right: 0; 164 | bottom: 0; 165 | left: 0; 166 | background: rgba(238, 228, 218, 0.5); 167 | z-index: 100; 168 | text-align: center; 169 | -webkit-animation: fade-in 800ms ease 1200ms; 170 | -moz-animation: fade-in 800ms ease 1200ms; 171 | animation: fade-in 800ms ease 1200ms; 172 | -webkit-animation-fill-mode: both; 173 | -moz-animation-fill-mode: both; 174 | animation-fill-mode: both; } 175 | .game-container .game-message p { 176 | font-size: 60px; 177 | font-weight: bold; 178 | height: 60px; 179 | line-height: 60px; 180 | margin-top: 163px; } 181 | .game-container .game-message .lower { 182 | display: block; 183 | margin-top: 59px; } 184 | .game-container .game-message a { 185 | display: inline-block; 186 | background: #8f7a66; 187 | border-radius: 3px; 188 | padding: 0 20px; 189 | text-decoration: none; 190 | color: #f9f6f2; 191 | height: 40px; 192 | line-height: 42px; 193 | margin-left: 9px; } 194 | .game-container .game-message a.keep-playing-button { 195 | display: none; } 196 | .game-container .game-message.game-won { 197 | background: rgba(237, 194, 46, 0.5); 198 | color: #f9f6f2; } 199 | .game-container .game-message.game-won a.keep-playing-button { 200 | display: inline-block; } 201 | .game-container .game-message.game-won, .game-container .game-message.game-over { 202 | display: block; } 203 | 204 | .grid-container { 205 | position: absolute; 206 | z-index: 1; } 207 | 208 | .grid-row { 209 | margin-bottom: 15px; } 210 | .grid-row:last-child { 211 | margin-bottom: 0; } 212 | .grid-row:after { 213 | content: ""; 214 | display: block; 215 | clear: both; } 216 | 217 | .grid-cell { 218 | width: 106.25px; 219 | height: 106.25px; 220 | margin-right: 15px; 221 | float: left; 222 | border-radius: 3px; 223 | background: rgba(238, 228, 218, 0.35); } 224 | .grid-cell:last-child { 225 | margin-right: 0; } 226 | 227 | .tile-container { 228 | position: absolute; 229 | z-index: 2; } 230 | 231 | .tile, .tile .tile-inner { 232 | width: 107px; 233 | height: 107px; 234 | line-height: 116.25px; } 235 | 236 | .tile { 237 | position: absolute; 238 | -webkit-transition: 100ms ease-in-out; 239 | -moz-transition: 100ms ease-in-out; 240 | transition: 100ms ease-in-out; 241 | -webkit-transition-property: -webkit-transform; 242 | -moz-transition-property: -moz-transform; 243 | transition-property: transform; } 244 | .tile .tile-inner { 245 | border-radius: 3px; 246 | background: #eee4da; 247 | text-align: center; 248 | font-weight: bold; 249 | z-index: 10; 250 | font-size: 55px; } 251 | .tile.tile-2 .tile-inner { 252 | background: #eee4da; 253 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); } 254 | .tile.tile-4 .tile-inner { 255 | background: #ede0c8; 256 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); } 257 | .tile.tile-8 .tile-inner { 258 | color: #f9f6f2; 259 | background: #f2b179; } 260 | .tile.tile-16 .tile-inner { 261 | color: #f9f6f2; 262 | background: #f59563; } 263 | .tile.tile-32 .tile-inner { 264 | color: #f9f6f2; 265 | background: #f67c5f; } 266 | .tile.tile-64 .tile-inner { 267 | color: #f9f6f2; 268 | background: #f65e3b; } 269 | .tile.tile-128 .tile-inner { 270 | color: #f9f6f2; 271 | background: #edcf72; 272 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.2381), inset 0 0 0 1px rgba(255, 255, 255, 0.14286); 273 | font-size: 45px; } 274 | @media screen and (max-width: 520px) { 275 | .tile.tile-128 .tile-inner { 276 | font-size: 25px; } } 277 | .tile.tile-256 .tile-inner { 278 | color: #f9f6f2; 279 | background: #edcc61; 280 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048); 281 | font-size: 45px; } 282 | @media screen and (max-width: 520px) { 283 | .tile.tile-256 .tile-inner { 284 | font-size: 25px; } } 285 | .tile.tile-512 .tile-inner { 286 | color: #f9f6f2; 287 | background: #edc850; 288 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381); 289 | font-size: 45px; } 290 | @media screen and (max-width: 520px) { 291 | .tile.tile-512 .tile-inner { 292 | font-size: 25px; } } 293 | .tile.tile-1024 .tile-inner { 294 | color: #f9f6f2; 295 | background: #edc53f; 296 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571); 297 | font-size: 35px; } 298 | @media screen and (max-width: 520px) { 299 | .tile.tile-1024 .tile-inner { 300 | font-size: 15px; } } 301 | .tile.tile-2048 .tile-inner { 302 | color: #f9f6f2; 303 | background: #edc22e; 304 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333); 305 | font-size: 35px; } 306 | @media screen and (max-width: 520px) { 307 | .tile.tile-2048 .tile-inner { 308 | font-size: 15px; } } 309 | .tile.tile-super .tile-inner { 310 | color: #f9f6f2; 311 | background: #3c3a32; 312 | font-size: 30px; } 313 | @media screen and (max-width: 520px) { 314 | .tile.tile-super .tile-inner { 315 | font-size: 10px; } } 316 | 317 | @-webkit-keyframes appear { 318 | 0% { 319 | opacity: 0; 320 | -webkit-transform: scale(0); 321 | -moz-transform: scale(0); 322 | transform: scale(0); } 323 | 324 | 100% { 325 | opacity: 1; 326 | -webkit-transform: scale(1); 327 | -moz-transform: scale(1); 328 | transform: scale(1); } } 329 | @-moz-keyframes appear { 330 | 0% { 331 | opacity: 0; 332 | -webkit-transform: scale(0); 333 | -moz-transform: scale(0); 334 | transform: scale(0); } 335 | 336 | 100% { 337 | opacity: 1; 338 | -webkit-transform: scale(1); 339 | -moz-transform: scale(1); 340 | transform: scale(1); } } 341 | @keyframes appear { 342 | 0% { 343 | opacity: 0; 344 | -webkit-transform: scale(0); 345 | -moz-transform: scale(0); 346 | transform: scale(0); } 347 | 348 | 100% { 349 | opacity: 1; 350 | -webkit-transform: scale(1); 351 | -moz-transform: scale(1); 352 | transform: scale(1); } } 353 | .tile-new .tile-inner { 354 | -webkit-animation: appear 200ms ease 100ms; 355 | -moz-animation: appear 200ms ease 100ms; 356 | animation: appear 200ms ease 100ms; 357 | -webkit-animation-fill-mode: backwards; 358 | -moz-animation-fill-mode: backwards; 359 | animation-fill-mode: backwards; } 360 | 361 | @-webkit-keyframes pop { 362 | 0% { 363 | -webkit-transform: scale(0); 364 | -moz-transform: scale(0); 365 | transform: scale(0); } 366 | 367 | 50% { 368 | -webkit-transform: scale(1.2); 369 | -moz-transform: scale(1.2); 370 | transform: scale(1.2); } 371 | 372 | 100% { 373 | -webkit-transform: scale(1); 374 | -moz-transform: scale(1); 375 | transform: scale(1); } } 376 | @-moz-keyframes pop { 377 | 0% { 378 | -webkit-transform: scale(0); 379 | -moz-transform: scale(0); 380 | transform: scale(0); } 381 | 382 | 50% { 383 | -webkit-transform: scale(1.2); 384 | -moz-transform: scale(1.2); 385 | transform: scale(1.2); } 386 | 387 | 100% { 388 | -webkit-transform: scale(1); 389 | -moz-transform: scale(1); 390 | transform: scale(1); } } 391 | @keyframes pop { 392 | 0% { 393 | -webkit-transform: scale(0); 394 | -moz-transform: scale(0); 395 | transform: scale(0); } 396 | 397 | 50% { 398 | -webkit-transform: scale(1.2); 399 | -moz-transform: scale(1.2); 400 | transform: scale(1.2); } 401 | 402 | 100% { 403 | -webkit-transform: scale(1); 404 | -moz-transform: scale(1); 405 | transform: scale(1); } } 406 | .tile-merged .tile-inner { 407 | z-index: 20; 408 | -webkit-animation: pop 200ms ease 100ms; 409 | -moz-animation: pop 200ms ease 100ms; 410 | animation: pop 200ms ease 100ms; 411 | -webkit-animation-fill-mode: backwards; 412 | -moz-animation-fill-mode: backwards; 413 | animation-fill-mode: backwards; } 414 | 415 | .above-game:after { 416 | content: ""; 417 | display: block; 418 | clear: both; } 419 | 420 | .game-intro { 421 | float: left; 422 | line-height: 42px; 423 | margin-bottom: 0; } 424 | 425 | .restart-button { 426 | display: inline-block; 427 | background: #8f7a66; 428 | border-radius: 3px; 429 | padding: 0 20px; 430 | text-decoration: none; 431 | color: #f9f6f2; 432 | height: 40px; 433 | line-height: 42px; 434 | display: block; 435 | text-align: center; 436 | float: right; } 437 | 438 | .game-explanation { 439 | margin-top: 50px; } 440 | 441 | @media screen and (max-width: 520px) { 442 | html, body { 443 | font-size: 15px; } 444 | 445 | body { 446 | margin: 20px 0; 447 | padding: 0 20px; } 448 | 449 | h1.title { 450 | font-size: 27px; 451 | margin-top: 15px; } 452 | 453 | .container { 454 | width: 280px; 455 | margin: 0 auto; } 456 | 457 | .score-container, .best-container { 458 | margin-top: 0; 459 | padding: 15px 10px; 460 | min-width: 40px; } 461 | 462 | .heading { 463 | margin-bottom: 10px; } 464 | 465 | .game-intro { 466 | width: 55%; 467 | display: block; 468 | box-sizing: border-box; 469 | line-height: 1.65; } 470 | 471 | .restart-button { 472 | width: 42%; 473 | padding: 0; 474 | display: block; 475 | box-sizing: border-box; 476 | margin-top: 2px; } 477 | 478 | .game-container { 479 | margin-top: 17px; 480 | position: relative; 481 | padding: 10px; 482 | cursor: default; 483 | -webkit-touch-callout: none; 484 | -ms-touch-callout: none; 485 | -webkit-user-select: none; 486 | -moz-user-select: none; 487 | -ms-user-select: none; 488 | -ms-touch-action: none; 489 | touch-action: none; 490 | background: #bbada0; 491 | border-radius: 6px; 492 | width: 280px; 493 | height: 280px; 494 | -webkit-box-sizing: border-box; 495 | -moz-box-sizing: border-box; 496 | box-sizing: border-box; } 497 | .game-container .game-message { 498 | display: none; 499 | position: absolute; 500 | top: 0; 501 | right: 0; 502 | bottom: 0; 503 | left: 0; 504 | background: rgba(238, 228, 218, 0.5); 505 | z-index: 100; 506 | text-align: center; 507 | -webkit-animation: fade-in 800ms ease 1200ms; 508 | -moz-animation: fade-in 800ms ease 1200ms; 509 | animation: fade-in 800ms ease 1200ms; 510 | -webkit-animation-fill-mode: both; 511 | -moz-animation-fill-mode: both; 512 | animation-fill-mode: both; } 513 | .game-container .game-message p { 514 | font-size: 60px; 515 | font-weight: bold; 516 | height: 60px; 517 | line-height: 60px; 518 | margin-top: 222px; } 519 | .game-container .game-message .lower { 520 | display: block; 521 | margin-top: 59px; } 522 | .game-container .game-message a { 523 | display: inline-block; 524 | background: #8f7a66; 525 | border-radius: 3px; 526 | padding: 0 20px; 527 | text-decoration: none; 528 | color: #f9f6f2; 529 | height: 40px; 530 | line-height: 42px; 531 | margin-left: 9px; } 532 | .game-container .game-message a.keep-playing-button { 533 | display: none; } 534 | .game-container .game-message.game-won { 535 | background: rgba(237, 194, 46, 0.5); 536 | color: #f9f6f2; } 537 | .game-container .game-message.game-won a.keep-playing-button { 538 | display: inline-block; } 539 | .game-container .game-message.game-won, .game-container .game-message.game-over { 540 | display: block; } 541 | 542 | .grid-container { 543 | position: absolute; 544 | z-index: 1; } 545 | 546 | .grid-row { 547 | margin-bottom: 10px; } 548 | .grid-row:last-child { 549 | margin-bottom: 0; } 550 | .grid-row:after { 551 | content: ""; 552 | display: block; 553 | clear: both; } 554 | 555 | .grid-cell { 556 | width: 57.5px; 557 | height: 57.5px; 558 | margin-right: 10px; 559 | float: left; 560 | border-radius: 3px; 561 | background: rgba(238, 228, 218, 0.35); } 562 | .grid-cell:last-child { 563 | margin-right: 0; } 564 | 565 | .tile-container { 566 | position: absolute; 567 | z-index: 2; } 568 | 569 | .tile, .tile .tile-inner { 570 | width: 58px; 571 | height: 58px; 572 | line-height: 67.5px; } 573 | 574 | .tile .tile-inner { 575 | font-size: 35px; } 576 | 577 | .game-message p { 578 | font-size: 30px !important; 579 | height: 30px !important; 580 | line-height: 30px !important; 581 | margin-top: 90px !important; } 582 | .game-message .lower { 583 | margin-top: 30px !important; } } 584 | 585 | select { 586 | padding:3px; 587 | margin: 0; 588 | -webkit-border-radius:4px; 589 | -moz-border-radius:4px; 590 | border-radius:4px; 591 | -webkit-box-shadow: 0 3px 0 #ccc, 0 -1px #fff inset; 592 | -moz-box-shadow: 0 3px 0 #ccc, 0 -1px #fff inset; 593 | box-shadow: 0 3px 0 #ccc, 0 -1px #fff inset; 594 | background: #8f7a66; 595 | color:#fff; 596 | border:none; 597 | outline:none; 598 | display: inline-block; 599 | -webkit-appearance:none; 600 | -moz-appearance:none; 601 | appearance:none; 602 | cursor:pointer; 603 | float:left; 604 | } 605 | .left{ 606 | clear: both; 607 | float: left; 608 | font-size: 17px; 609 | line-height: 24px; 610 | padding: 0 10px 0 10px; 611 | } 612 | [v-cloak] { display: none; background: red; } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 2048 Game 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |

2048

14 |
15 |
{{conf.score}}
16 |
{{conf.bestScore}}
17 |
18 |
19 | 20 |
21 |

Join the numbers and get to the 2048 tile!

22 | New Game 23 | 24 | 25 | 30 |
31 | 32 |
33 |
34 |

35 |
36 | Keep going 37 | Try again 38 |
39 |
40 | 41 |
42 | 43 |
44 |
45 | 46 |
47 |
48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 |
56 |
{{value}}
57 |
58 | 59 | 60 | 63 | 64 |
65 | 66 |
67 |

68 | How to play: Use your arrow keys to move the tiles. When two tiles with the same number touch, they merge into one! 69 |

70 |
71 | 72 |

73 | NOTE: This game was written using Vue.js 74 |

75 | 76 |
77 | 78 |

79 | Created by Achilleas Kiritsakas based on 2048 by Gabriele Cirulli. Based on 1024 by Veewo Studio and conceptually similar to Threes by Asher Vollmer. 80 |

81 | 82 |
83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /js/keys.js: -------------------------------------------------------------------------------- 1 | function KeyboardInputManager() { 2 | this.events = {}; 3 | 4 | if (window.navigator.msPointerEnabled) { 5 | //Internet Explorer 10 style 6 | this.eventTouchstart = "MSPointerDown"; 7 | this.eventTouchmove = "MSPointerMove"; 8 | this.eventTouchend = "MSPointerUp"; 9 | } else { 10 | this.eventTouchstart = "touchstart"; 11 | this.eventTouchmove = "touchmove"; 12 | this.eventTouchend = "touchend"; 13 | } 14 | 15 | this.listen(); 16 | } 17 | 18 | KeyboardInputManager.prototype.on = function (event, callback) { 19 | if (!this.events[event]) { 20 | this.events[event] = []; 21 | } 22 | this.events[event].push(callback); 23 | }; 24 | 25 | KeyboardInputManager.prototype.emit = function (event, data) { 26 | var callbacks = this.events[event]; 27 | if (callbacks) { 28 | callbacks.forEach(function (callback) { 29 | callback(data); 30 | }); 31 | } 32 | }; 33 | 34 | KeyboardInputManager.prototype.listen = function () { 35 | var self = this; 36 | 37 | var map = { 38 | 38: 0, // Up 39 | 39: 1, // Right 40 | 40: 2, // Down 41 | 37: 3, // Left 42 | 75: 0, // Vim up 43 | 76: 1, // Vim right 44 | 74: 2, // Vim down 45 | 72: 3, // Vim left 46 | 87: 0, // W 47 | 68: 1, // D 48 | 83: 2, // S 49 | 65: 3 // A 50 | }; 51 | 52 | // Respond to direction keys 53 | document.addEventListener("keydown", function (event) { 54 | 55 | var modifiers = event.altKey || event.ctrlKey || event.metaKey || 56 | event.shiftKey; 57 | var mapped = map[event.which]; 58 | 59 | if (!modifiers) { 60 | if (mapped !== undefined) { 61 | event.preventDefault(); 62 | self.emit("move", mapped); 63 | } 64 | } 65 | 66 | // R key restarts the game 67 | if (!modifiers && event.which === 82) { 68 | self.restart.call(self, event); 69 | } 70 | }); 71 | 72 | // Respond to button presses 73 | // this.bindButtonPress(".retry-button", this.restart); 74 | // this.bindButtonPress(".restart-button", this.restart); 75 | // this.bindButtonPress(".keep-playing-button", this.keepPlaying); 76 | 77 | // Respond to swipe events 78 | var touchStartClientX, touchStartClientY; 79 | var gameContainer = document.getElementsByClassName("game-container")[0]; 80 | 81 | gameContainer.addEventListener(this.eventTouchstart, function (event) { 82 | if ((!window.navigator.msPointerEnabled && event.touches.length > 1) || 83 | event.targetTouches > 1) { 84 | return; // Ignore if touching with more than 1 finger 85 | } 86 | 87 | if (window.navigator.msPointerEnabled) { 88 | touchStartClientX = event.pageX; 89 | touchStartClientY = event.pageY; 90 | } else { 91 | touchStartClientX = event.touches[0].clientX; 92 | touchStartClientY = event.touches[0].clientY; 93 | } 94 | 95 | event.preventDefault(); 96 | }); 97 | 98 | gameContainer.addEventListener(this.eventTouchmove, function (event) { 99 | event.preventDefault(); 100 | }); 101 | 102 | gameContainer.addEventListener(this.eventTouchend, function (event) { 103 | if ((!window.navigator.msPointerEnabled && event.touches.length > 0) || 104 | event.targetTouches > 0) { 105 | return; // Ignore if still touching with one or more fingers 106 | } 107 | 108 | var touchEndClientX, touchEndClientY; 109 | 110 | if (window.navigator.msPointerEnabled) { 111 | touchEndClientX = event.pageX; 112 | touchEndClientY = event.pageY; 113 | } else { 114 | touchEndClientX = event.changedTouches[0].clientX; 115 | touchEndClientY = event.changedTouches[0].clientY; 116 | } 117 | 118 | var dx = touchEndClientX - touchStartClientX; 119 | var absDx = Math.abs(dx); 120 | 121 | var dy = touchEndClientY - touchStartClientY; 122 | var absDy = Math.abs(dy); 123 | 124 | if (Math.max(absDx, absDy) > 10) { 125 | // (right : left) : (down : up) 126 | self.emit("move", absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0)); 127 | } 128 | }); 129 | }; 130 | 131 | // KeyboardInputManager.prototype.restart = function (event) { 132 | // event.preventDefault(); 133 | // this.emit("restart"); 134 | // }; 135 | 136 | // KeyboardInputManager.prototype.keepPlaying = function (event) { 137 | // event.preventDefault(); 138 | // this.emit("keepPlaying"); 139 | // }; 140 | 141 | // KeyboardInputManager.prototype.bindButtonPress = function (selector, fn) { 142 | // var button = document.querySelector(selector); 143 | // button.addEventListener("click", fn.bind(this)); 144 | // button.addEventListener(this.eventTouchend, fn.bind(this)); 145 | // }; 146 | -------------------------------------------------------------------------------- /js/libs/vue.js: -------------------------------------------------------------------------------- 1 | /* 2 | Vue.js v0.10.3 3 | (c) 2014 Evan You 4 | License: MIT 5 | */ 6 | ;(function(){ 7 | 'use strict'; 8 | 9 | /** 10 | * Require the given path. 11 | * 12 | * @param {String} path 13 | * @return {Object} exports 14 | * @api public 15 | */ 16 | 17 | function require(path, parent, orig) { 18 | var resolved = require.resolve(path); 19 | 20 | // lookup failed 21 | if (null == resolved) { 22 | throwError() 23 | return 24 | } 25 | 26 | var module = require.modules[resolved]; 27 | 28 | // perform real require() 29 | // by invoking the module's 30 | // registered function 31 | if (!module._resolving && !module.exports) { 32 | var mod = {}; 33 | mod.exports = {}; 34 | mod.client = mod.component = true; 35 | module._resolving = true; 36 | module.call(this, mod.exports, require.relative(resolved), mod); 37 | delete module._resolving; 38 | module.exports = mod.exports; 39 | } 40 | 41 | function throwError () { 42 | orig = orig || path; 43 | parent = parent || 'root'; 44 | var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); 45 | err.path = orig; 46 | err.parent = parent; 47 | err.require = true; 48 | throw err; 49 | } 50 | 51 | return module.exports; 52 | } 53 | 54 | /** 55 | * Registered modules. 56 | */ 57 | 58 | require.modules = {}; 59 | 60 | /** 61 | * Registered aliases. 62 | */ 63 | 64 | require.aliases = {}; 65 | 66 | /** 67 | * Resolve `path`. 68 | * 69 | * Lookup: 70 | * 71 | * - PATH/index.js 72 | * - PATH.js 73 | * - PATH 74 | * 75 | * @param {String} path 76 | * @return {String} path or null 77 | * @api private 78 | */ 79 | 80 | require.exts = [ 81 | '', 82 | '.js', 83 | '.json', 84 | '/index.js', 85 | '/index.json' 86 | ]; 87 | 88 | require.resolve = function(path) { 89 | if (path.charAt(0) === '/') path = path.slice(1); 90 | 91 | for (var i = 0; i < 5; i++) { 92 | var fullPath = path + require.exts[i]; 93 | if (require.modules.hasOwnProperty(fullPath)) return fullPath; 94 | if (require.aliases.hasOwnProperty(fullPath)) return require.aliases[fullPath]; 95 | } 96 | }; 97 | 98 | /** 99 | * Normalize `path` relative to the current path. 100 | * 101 | * @param {String} curr 102 | * @param {String} path 103 | * @return {String} 104 | * @api private 105 | */ 106 | 107 | require.normalize = function(curr, path) { 108 | 109 | var segs = []; 110 | 111 | if ('.' != path.charAt(0)) return path; 112 | 113 | curr = curr.split('/'); 114 | path = path.split('/'); 115 | 116 | for (var i = 0; i < path.length; ++i) { 117 | if ('..' === path[i]) { 118 | curr.pop(); 119 | } else if ('.' != path[i] && '' != path[i]) { 120 | segs.push(path[i]); 121 | } 122 | } 123 | return curr.concat(segs).join('/'); 124 | }; 125 | 126 | /** 127 | * Register module at `path` with callback `definition`. 128 | * 129 | * @param {String} path 130 | * @param {Function} definition 131 | * @api private 132 | */ 133 | 134 | require.register = function(path, definition) { 135 | require.modules[path] = definition; 136 | }; 137 | 138 | /** 139 | * Alias a module definition. 140 | * 141 | * @param {String} from 142 | * @param {String} to 143 | * @api private 144 | */ 145 | 146 | require.alias = function(from, to) { 147 | if (!require.modules.hasOwnProperty(from)) { 148 | throwError() 149 | return 150 | } 151 | require.aliases[to] = from; 152 | 153 | function throwError () { 154 | throw new Error('Failed to alias "' + from + '", it does not exist'); 155 | } 156 | }; 157 | 158 | /** 159 | * Return a require function relative to the `parent` path. 160 | * 161 | * @param {String} parent 162 | * @return {Function} 163 | * @api private 164 | */ 165 | 166 | require.relative = function(parent) { 167 | var p = require.normalize(parent, '..'); 168 | 169 | /** 170 | * The relative require() itself. 171 | */ 172 | 173 | function localRequire(path) { 174 | var resolved = localRequire.resolve(path); 175 | return require(resolved, parent, path); 176 | } 177 | 178 | /** 179 | * Resolve relative to the parent. 180 | */ 181 | 182 | localRequire.resolve = function(path) { 183 | var c = path.charAt(0); 184 | if ('/' === c) return path.slice(1); 185 | if ('.' === c) return require.normalize(p, path); 186 | 187 | // resolve deps by returning 188 | // the dep in the nearest "deps" 189 | // directory 190 | var segs = parent.split('/'); 191 | var i = segs.length; 192 | while (i--) { 193 | if (segs[i] === 'deps') { 194 | break; 195 | } 196 | } 197 | path = segs.slice(0, i + 2).join('/') + '/deps/' + path; 198 | return path; 199 | }; 200 | 201 | /** 202 | * Check if module is defined at `path`. 203 | */ 204 | 205 | localRequire.exists = function(path) { 206 | return require.modules.hasOwnProperty(localRequire.resolve(path)); 207 | }; 208 | 209 | return localRequire; 210 | }; 211 | require.register("vue/src/main.js", function(exports, require, module){ 212 | var config = require('./config'), 213 | ViewModel = require('./viewmodel'), 214 | utils = require('./utils'), 215 | makeHash = utils.hash, 216 | assetTypes = ['directive', 'filter', 'partial', 'effect', 'component'] 217 | 218 | // require these so Browserify can catch them 219 | // so they can be used in Vue.require 220 | require('./observer') 221 | require('./transition') 222 | 223 | ViewModel.options = config.globalAssets = { 224 | directives : require('./directives'), 225 | filters : require('./filters'), 226 | partials : makeHash(), 227 | effects : makeHash(), 228 | components : makeHash() 229 | } 230 | 231 | /** 232 | * Expose asset registration methods 233 | */ 234 | assetTypes.forEach(function (type) { 235 | ViewModel[type] = function (id, value) { 236 | var hash = this.options[type + 's'] 237 | if (!hash) { 238 | hash = this.options[type + 's'] = makeHash() 239 | } 240 | if (!value) return hash[id] 241 | if (type === 'partial') { 242 | value = utils.toFragment(value) 243 | } else if (type === 'component') { 244 | value = utils.toConstructor(value) 245 | } else if (type === 'filter') { 246 | utils.checkFilter(value) 247 | } 248 | hash[id] = value 249 | return this 250 | } 251 | }) 252 | 253 | /** 254 | * Set config options 255 | */ 256 | ViewModel.config = function (opts, val) { 257 | if (typeof opts === 'string') { 258 | if (val === undefined) { 259 | return config[opts] 260 | } else { 261 | config[opts] = val 262 | } 263 | } else { 264 | utils.extend(config, opts) 265 | } 266 | return this 267 | } 268 | 269 | /** 270 | * Expose an interface for plugins 271 | */ 272 | ViewModel.use = function (plugin) { 273 | if (typeof plugin === 'string') { 274 | try { 275 | plugin = require(plugin) 276 | } catch (e) { 277 | utils.warn('Cannot find plugin: ' + plugin) 278 | return 279 | } 280 | } 281 | 282 | // additional parameters 283 | var args = [].slice.call(arguments, 1) 284 | args.unshift(this) 285 | 286 | if (typeof plugin.install === 'function') { 287 | plugin.install.apply(plugin, args) 288 | } else { 289 | plugin.apply(null, args) 290 | } 291 | return this 292 | } 293 | 294 | /** 295 | * Expose internal modules for plugins 296 | */ 297 | ViewModel.require = function (path) { 298 | return require('./' + path) 299 | } 300 | 301 | ViewModel.extend = extend 302 | ViewModel.nextTick = utils.nextTick 303 | 304 | /** 305 | * Expose the main ViewModel class 306 | * and add extend method 307 | */ 308 | function extend (options) { 309 | 310 | var ParentVM = this 311 | 312 | // extend data options need to be copied 313 | // on instantiation 314 | if (options.data) { 315 | options.defaultData = options.data 316 | delete options.data 317 | } 318 | 319 | // inherit options 320 | options = inheritOptions(options, ParentVM.options, true) 321 | utils.processOptions(options) 322 | 323 | var ExtendedVM = function (opts, asParent) { 324 | if (!asParent) { 325 | opts = inheritOptions(opts, options, true) 326 | } 327 | ParentVM.call(this, opts, true) 328 | } 329 | 330 | // inherit prototype props 331 | var proto = ExtendedVM.prototype = Object.create(ParentVM.prototype) 332 | utils.defProtected(proto, 'constructor', ExtendedVM) 333 | 334 | // allow extended VM to be further extended 335 | ExtendedVM.extend = extend 336 | ExtendedVM.super = ParentVM 337 | ExtendedVM.options = options 338 | 339 | // allow extended VM to add its own assets 340 | assetTypes.forEach(function (type) { 341 | ExtendedVM[type] = ViewModel[type] 342 | }) 343 | 344 | // allow extended VM to use plugins 345 | ExtendedVM.use = ViewModel.use 346 | ExtendedVM.require = ViewModel.require 347 | 348 | return ExtendedVM 349 | } 350 | 351 | /** 352 | * Inherit options 353 | * 354 | * For options such as `data`, `vms`, `directives`, 'partials', 355 | * they should be further extended. However extending should only 356 | * be done at top level. 357 | * 358 | * `proto` is an exception because it's handled directly on the 359 | * prototype. 360 | * 361 | * `el` is an exception because it's not allowed as an 362 | * extension option, but only as an instance option. 363 | */ 364 | function inheritOptions (child, parent, topLevel) { 365 | child = child || {} 366 | if (!parent) return child 367 | for (var key in parent) { 368 | if (key === 'el') continue 369 | var val = child[key], 370 | parentVal = parent[key] 371 | if (topLevel && typeof val === 'function' && parentVal) { 372 | // merge hook functions into an array 373 | child[key] = [val] 374 | if (Array.isArray(parentVal)) { 375 | child[key] = child[key].concat(parentVal) 376 | } else { 377 | child[key].push(parentVal) 378 | } 379 | } else if ( 380 | topLevel && 381 | (utils.isTrueObject(val) || utils.isTrueObject(parentVal)) 382 | && !(parentVal instanceof ViewModel) 383 | ) { 384 | // merge toplevel object options 385 | child[key] = inheritOptions(val, parentVal) 386 | } else if (val === undefined) { 387 | // inherit if child doesn't override 388 | child[key] = parentVal 389 | } 390 | } 391 | return child 392 | } 393 | 394 | module.exports = ViewModel 395 | }); 396 | require.register("vue/src/emitter.js", function(exports, require, module){ 397 | function Emitter (ctx) { 398 | this._ctx = ctx || this 399 | } 400 | 401 | var EmitterProto = Emitter.prototype 402 | 403 | EmitterProto.on = function(event, fn){ 404 | this._cbs = this._cbs || {} 405 | ;(this._cbs[event] = this._cbs[event] || []) 406 | .push(fn) 407 | return this 408 | } 409 | 410 | EmitterProto.once = function(event, fn){ 411 | var self = this 412 | this._cbs = this._cbs || {} 413 | 414 | function on () { 415 | self.off(event, on) 416 | fn.apply(this, arguments) 417 | } 418 | 419 | on.fn = fn 420 | this.on(event, on) 421 | return this 422 | } 423 | 424 | EmitterProto.off = function(event, fn){ 425 | this._cbs = this._cbs || {} 426 | 427 | // all 428 | if (!arguments.length) { 429 | this._cbs = {} 430 | return this 431 | } 432 | 433 | // specific event 434 | var callbacks = this._cbs[event] 435 | if (!callbacks) return this 436 | 437 | // remove all handlers 438 | if (arguments.length === 1) { 439 | delete this._cbs[event] 440 | return this 441 | } 442 | 443 | // remove specific handler 444 | var cb 445 | for (var i = 0; i < callbacks.length; i++) { 446 | cb = callbacks[i] 447 | if (cb === fn || cb.fn === fn) { 448 | callbacks.splice(i, 1) 449 | break 450 | } 451 | } 452 | return this 453 | } 454 | 455 | EmitterProto.emit = function(event, a, b, c){ 456 | this._cbs = this._cbs || {} 457 | var callbacks = this._cbs[event] 458 | 459 | if (callbacks) { 460 | callbacks = callbacks.slice(0) 461 | for (var i = 0, len = callbacks.length; i < len; i++) { 462 | callbacks[i].call(this._ctx, a, b, c) 463 | } 464 | } 465 | 466 | return this 467 | } 468 | 469 | module.exports = Emitter 470 | }); 471 | require.register("vue/src/config.js", function(exports, require, module){ 472 | var TextParser = require('./text-parser') 473 | 474 | module.exports = { 475 | prefix : 'v', 476 | debug : false, 477 | silent : false, 478 | enterClass : 'v-enter', 479 | leaveClass : 'v-leave', 480 | interpolate : true 481 | } 482 | 483 | Object.defineProperty(module.exports, 'delimiters', { 484 | get: function () { 485 | return TextParser.delimiters 486 | }, 487 | set: function (delimiters) { 488 | TextParser.setDelimiters(delimiters) 489 | } 490 | }) 491 | }); 492 | require.register("vue/src/utils.js", function(exports, require, module){ 493 | var config = require('./config'), 494 | toString = ({}).toString, 495 | win = window, 496 | console = win.console, 497 | timeout = win.setTimeout, 498 | def = Object.defineProperty, 499 | THIS_RE = /[^\w]this[^\w]/, 500 | OBJECT = 'object', 501 | hasClassList = 'classList' in document.documentElement, 502 | ViewModel // late def 503 | 504 | var utils = module.exports = { 505 | 506 | /** 507 | * get a value from an object keypath 508 | */ 509 | get: function (obj, key) { 510 | /* jshint eqeqeq: false */ 511 | if (key.indexOf('.') < 0) { 512 | return obj[key] 513 | } 514 | var path = key.split('.'), 515 | d = -1, l = path.length 516 | while (++d < l && obj != null) { 517 | obj = obj[path[d]] 518 | } 519 | return obj 520 | }, 521 | 522 | /** 523 | * set a value to an object keypath 524 | */ 525 | set: function (obj, key, val) { 526 | /* jshint eqeqeq: false */ 527 | if (key.indexOf('.') < 0) { 528 | obj[key] = val 529 | return 530 | } 531 | var path = key.split('.'), 532 | d = -1, l = path.length - 1 533 | while (++d < l) { 534 | if (obj[path[d]] == null) { 535 | obj[path[d]] = {} 536 | } 537 | obj = obj[path[d]] 538 | } 539 | obj[path[d]] = val 540 | }, 541 | 542 | /** 543 | * return the base segment of a keypath 544 | */ 545 | baseKey: function (key) { 546 | return key.indexOf('.') > 0 547 | ? key.split('.')[0] 548 | : key 549 | }, 550 | 551 | /** 552 | * Create a prototype-less object 553 | * which is a better hash/map 554 | */ 555 | hash: function () { 556 | return Object.create(null) 557 | }, 558 | 559 | /** 560 | * get an attribute and remove it. 561 | */ 562 | attr: function (el, type) { 563 | var attr = config.prefix + '-' + type, 564 | val = el.getAttribute(attr) 565 | if (val !== null) { 566 | el.removeAttribute(attr) 567 | } 568 | return val 569 | }, 570 | 571 | /** 572 | * Define an ienumerable property 573 | * This avoids it being included in JSON.stringify 574 | * or for...in loops. 575 | */ 576 | defProtected: function (obj, key, val, enumerable, writable) { 577 | def(obj, key, { 578 | value : val, 579 | enumerable : enumerable, 580 | writable : writable, 581 | configurable : true 582 | }) 583 | }, 584 | 585 | /** 586 | * A less bullet-proof but more efficient type check 587 | * than Object.prototype.toString 588 | */ 589 | isObject: function (obj) { 590 | return typeof obj === OBJECT && obj && !Array.isArray(obj) 591 | }, 592 | 593 | /** 594 | * A more accurate but less efficient type check 595 | */ 596 | isTrueObject: function (obj) { 597 | return toString.call(obj) === '[object Object]' 598 | }, 599 | 600 | /** 601 | * Most simple bind 602 | * enough for the usecase and fast than native bind() 603 | */ 604 | bind: function (fn, ctx) { 605 | return function (arg) { 606 | return fn.call(ctx, arg) 607 | } 608 | }, 609 | 610 | /** 611 | * Make sure null and undefined output empty string 612 | */ 613 | guard: function (value) { 614 | /* jshint eqeqeq: false, eqnull: true */ 615 | return value == null 616 | ? '' 617 | : (typeof value == 'object') 618 | ? JSON.stringify(value) 619 | : value 620 | }, 621 | 622 | /** 623 | * When setting value on the VM, parse possible numbers 624 | */ 625 | checkNumber: function (value) { 626 | return (isNaN(value) || value === null || typeof value === 'boolean') 627 | ? value 628 | : Number(value) 629 | }, 630 | 631 | /** 632 | * simple extend 633 | */ 634 | extend: function (obj, ext) { 635 | for (var key in ext) { 636 | if (obj[key] !== ext[key]) { 637 | obj[key] = ext[key] 638 | } 639 | } 640 | return obj 641 | }, 642 | 643 | /** 644 | * filter an array with duplicates into uniques 645 | */ 646 | unique: function (arr) { 647 | var hash = utils.hash(), 648 | i = arr.length, 649 | key, res = [] 650 | while (i--) { 651 | key = arr[i] 652 | if (hash[key]) continue 653 | hash[key] = 1 654 | res.push(key) 655 | } 656 | return res 657 | }, 658 | 659 | /** 660 | * Convert a string template to a dom fragment 661 | */ 662 | toFragment: function (template) { 663 | if (typeof template !== 'string') { 664 | return template 665 | } 666 | if (template.charAt(0) === '#') { 667 | var templateNode = document.getElementById(template.slice(1)) 668 | if (!templateNode) return 669 | // if its a template tag and the browser supports it, 670 | // its content is already a document fragment! 671 | if (templateNode.tagName === 'TEMPLATE' && templateNode.content) { 672 | return templateNode.content 673 | } 674 | template = templateNode.innerHTML 675 | } 676 | var node = document.createElement('div'), 677 | frag = document.createDocumentFragment(), 678 | child 679 | node.innerHTML = template.trim() 680 | /* jshint boss: true */ 681 | while (child = node.firstChild) { 682 | if (node.nodeType === 1) { 683 | frag.appendChild(child) 684 | } 685 | } 686 | return frag 687 | }, 688 | 689 | /** 690 | * Convert the object to a ViewModel constructor 691 | * if it is not already one 692 | */ 693 | toConstructor: function (obj) { 694 | ViewModel = ViewModel || require('./viewmodel') 695 | return utils.isObject(obj) 696 | ? ViewModel.extend(obj) 697 | : typeof obj === 'function' 698 | ? obj 699 | : null 700 | }, 701 | 702 | /** 703 | * Check if a filter function contains references to `this` 704 | * If yes, mark it as a computed filter. 705 | */ 706 | checkFilter: function (filter) { 707 | if (THIS_RE.test(filter.toString())) { 708 | filter.computed = true 709 | } 710 | }, 711 | 712 | /** 713 | * convert certain option values to the desired format. 714 | */ 715 | processOptions: function (options) { 716 | var components = options.components, 717 | partials = options.partials, 718 | template = options.template, 719 | filters = options.filters, 720 | key 721 | if (components) { 722 | for (key in components) { 723 | components[key] = utils.toConstructor(components[key]) 724 | } 725 | } 726 | if (partials) { 727 | for (key in partials) { 728 | partials[key] = utils.toFragment(partials[key]) 729 | } 730 | } 731 | if (filters) { 732 | for (key in filters) { 733 | utils.checkFilter(filters[key]) 734 | } 735 | } 736 | if (template) { 737 | options.template = utils.toFragment(template) 738 | } 739 | }, 740 | 741 | /** 742 | * used to defer batch updates 743 | */ 744 | nextTick: function (cb) { 745 | timeout(cb, 0) 746 | }, 747 | 748 | /** 749 | * add class for IE9 750 | * uses classList if available 751 | */ 752 | addClass: function (el, cls) { 753 | if (hasClassList) { 754 | el.classList.add(cls) 755 | } else { 756 | var cur = ' ' + el.className + ' ' 757 | if (cur.indexOf(' ' + cls + ' ') < 0) { 758 | el.className = (cur + cls).trim() 759 | } 760 | } 761 | }, 762 | 763 | /** 764 | * remove class for IE9 765 | */ 766 | removeClass: function (el, cls) { 767 | if (hasClassList) { 768 | el.classList.remove(cls) 769 | } else { 770 | var cur = ' ' + el.className + ' ', 771 | tar = ' ' + cls + ' ' 772 | while (cur.indexOf(tar) >= 0) { 773 | cur = cur.replace(tar, ' ') 774 | } 775 | el.className = cur.trim() 776 | } 777 | }, 778 | 779 | /** 780 | * Convert an object to Array 781 | * used in v-repeat and array filters 782 | */ 783 | objectToArray: function (obj) { 784 | var res = [], val, data 785 | for (var key in obj) { 786 | val = obj[key] 787 | data = utils.isObject(val) 788 | ? val 789 | : { $value: val } 790 | data.$key = key 791 | res.push(data) 792 | } 793 | return res 794 | } 795 | } 796 | 797 | enableDebug() 798 | function enableDebug () { 799 | /** 800 | * log for debugging 801 | */ 802 | utils.log = function (msg) { 803 | if (config.debug && console) { 804 | console.log(msg) 805 | } 806 | } 807 | 808 | /** 809 | * warnings, traces by default 810 | * can be suppressed by `silent` option. 811 | */ 812 | utils.warn = function (msg) { 813 | if (!config.silent && console) { 814 | console.warn(msg) 815 | if (config.debug && console.trace) { 816 | console.trace() 817 | } 818 | } 819 | } 820 | } 821 | }); 822 | require.register("vue/src/compiler.js", function(exports, require, module){ 823 | var Emitter = require('./emitter'), 824 | Observer = require('./observer'), 825 | config = require('./config'), 826 | utils = require('./utils'), 827 | Binding = require('./binding'), 828 | Directive = require('./directive'), 829 | TextParser = require('./text-parser'), 830 | DepsParser = require('./deps-parser'), 831 | ExpParser = require('./exp-parser'), 832 | ViewModel, 833 | 834 | // cache methods 835 | slice = [].slice, 836 | extend = utils.extend, 837 | hasOwn = ({}).hasOwnProperty, 838 | def = Object.defineProperty, 839 | 840 | // hooks to register 841 | hooks = [ 842 | 'created', 'ready', 843 | 'beforeDestroy', 'afterDestroy', 844 | 'attached', 'detached' 845 | ], 846 | 847 | // list of priority directives 848 | // that needs to be checked in specific order 849 | priorityDirectives = [ 850 | 'if', 851 | 'repeat', 852 | 'view', 853 | 'component' 854 | ] 855 | 856 | /** 857 | * The DOM compiler 858 | * scans a DOM node and compile bindings for a ViewModel 859 | */ 860 | function Compiler (vm, options) { 861 | 862 | var compiler = this, 863 | key, i 864 | 865 | // default state 866 | compiler.init = true 867 | compiler.destroyed = false 868 | 869 | // process and extend options 870 | options = compiler.options = options || {} 871 | utils.processOptions(options) 872 | 873 | // copy compiler options 874 | extend(compiler, options.compilerOptions) 875 | // repeat indicates this is a v-repeat instance 876 | compiler.repeat = compiler.repeat || false 877 | // expCache will be shared between v-repeat instances 878 | compiler.expCache = compiler.expCache || {} 879 | 880 | // initialize element 881 | var el = compiler.el = compiler.setupElement(options) 882 | utils.log('\nnew VM instance: ' + el.tagName + '\n') 883 | 884 | // set other compiler properties 885 | compiler.vm = el.vue_vm = vm 886 | compiler.bindings = utils.hash() 887 | compiler.dirs = [] 888 | compiler.deferred = [] 889 | compiler.computed = [] 890 | compiler.children = [] 891 | compiler.emitter = new Emitter(vm) 892 | 893 | // create bindings for computed properties 894 | if (options.methods) { 895 | for (key in options.methods) { 896 | compiler.createBinding(key) 897 | } 898 | } 899 | 900 | // create bindings for methods 901 | if (options.computed) { 902 | for (key in options.computed) { 903 | compiler.createBinding(key) 904 | } 905 | } 906 | 907 | // VM --------------------------------------------------------------------- 908 | 909 | // set VM properties 910 | vm.$ = {} 911 | vm.$el = el 912 | vm.$options = options 913 | vm.$compiler = compiler 914 | vm.$event = null 915 | 916 | // set parent & root 917 | var parentVM = options.parent 918 | if (parentVM) { 919 | compiler.parent = parentVM.$compiler 920 | parentVM.$compiler.children.push(compiler) 921 | vm.$parent = parentVM 922 | } 923 | vm.$root = getRoot(compiler).vm 924 | 925 | // DATA ------------------------------------------------------------------- 926 | 927 | // setup observer 928 | // this is necesarry for all hooks and data observation events 929 | compiler.setupObserver() 930 | 931 | // initialize data 932 | var data = compiler.data = options.data || {}, 933 | defaultData = options.defaultData 934 | if (defaultData) { 935 | for (key in defaultData) { 936 | if (!hasOwn.call(data, key)) { 937 | data[key] = defaultData[key] 938 | } 939 | } 940 | } 941 | 942 | // copy paramAttributes 943 | var params = options.paramAttributes 944 | if (params) { 945 | i = params.length 946 | while (i--) { 947 | data[params[i]] = utils.checkNumber( 948 | compiler.eval( 949 | el.getAttribute(params[i]) 950 | ) 951 | ) 952 | } 953 | } 954 | 955 | // copy data properties to vm 956 | // so user can access them in the created hook 957 | extend(vm, data) 958 | vm.$data = data 959 | 960 | // beforeCompile hook 961 | compiler.execHook('created') 962 | 963 | // the user might have swapped the data ... 964 | data = compiler.data = vm.$data 965 | 966 | // user might also set some properties on the vm 967 | // in which case we should copy back to $data 968 | var vmProp 969 | for (key in vm) { 970 | vmProp = vm[key] 971 | if ( 972 | key.charAt(0) !== '$' && 973 | data[key] !== vmProp && 974 | typeof vmProp !== 'function' 975 | ) { 976 | data[key] = vmProp 977 | } 978 | } 979 | 980 | // now we can observe the data. 981 | // this will convert data properties to getter/setters 982 | // and emit the first batch of set events, which will 983 | // in turn create the corresponding bindings. 984 | compiler.observeData(data) 985 | 986 | // COMPILE ---------------------------------------------------------------- 987 | 988 | // before compiling, resolve content insertion points 989 | if (options.template) { 990 | this.resolveContent() 991 | } 992 | 993 | // now parse the DOM and bind directives. 994 | // During this stage, we will also create bindings for 995 | // encountered keypaths that don't have a binding yet. 996 | compiler.compile(el, true) 997 | 998 | // Any directive that creates child VMs are deferred 999 | // so that when they are compiled, all bindings on the 1000 | // parent VM have been created. 1001 | i = compiler.deferred.length 1002 | while (i--) { 1003 | compiler.bindDirective(compiler.deferred[i]) 1004 | } 1005 | compiler.deferred = null 1006 | 1007 | // extract dependencies for computed properties. 1008 | // this will evaluated all collected computed bindings 1009 | // and collect get events that are emitted. 1010 | if (this.computed.length) { 1011 | DepsParser.parse(this.computed) 1012 | } 1013 | 1014 | // done! 1015 | compiler.init = false 1016 | 1017 | // post compile / ready hook 1018 | compiler.execHook('ready') 1019 | } 1020 | 1021 | var CompilerProto = Compiler.prototype 1022 | 1023 | /** 1024 | * Initialize the VM/Compiler's element. 1025 | * Fill it in with the template if necessary. 1026 | */ 1027 | CompilerProto.setupElement = function (options) { 1028 | // create the node first 1029 | var el = typeof options.el === 'string' 1030 | ? document.querySelector(options.el) 1031 | : options.el || document.createElement(options.tagName || 'div') 1032 | 1033 | var template = options.template, 1034 | child, replacer, i, attr, attrs 1035 | 1036 | if (template) { 1037 | // collect anything already in there 1038 | if (el.hasChildNodes()) { 1039 | this.rawContent = document.createElement('div') 1040 | /* jshint boss: true */ 1041 | while (child = el.firstChild) { 1042 | this.rawContent.appendChild(child) 1043 | } 1044 | } 1045 | // replace option: use the first node in 1046 | // the template directly 1047 | if (options.replace && template.childNodes.length === 1) { 1048 | replacer = template.childNodes[0].cloneNode(true) 1049 | if (el.parentNode) { 1050 | el.parentNode.insertBefore(replacer, el) 1051 | el.parentNode.removeChild(el) 1052 | } 1053 | // copy over attributes 1054 | if (el.hasAttributes()) { 1055 | i = el.attributes.length 1056 | while (i--) { 1057 | attr = el.attributes[i] 1058 | replacer.setAttribute(attr.name, attr.value) 1059 | } 1060 | } 1061 | // replace 1062 | el = replacer 1063 | } else { 1064 | el.appendChild(template.cloneNode(true)) 1065 | } 1066 | 1067 | } 1068 | 1069 | // apply element options 1070 | if (options.id) el.id = options.id 1071 | if (options.className) el.className = options.className 1072 | attrs = options.attributes 1073 | if (attrs) { 1074 | for (attr in attrs) { 1075 | el.setAttribute(attr, attrs[attr]) 1076 | } 1077 | } 1078 | 1079 | return el 1080 | } 1081 | 1082 | /** 1083 | * Deal with insertion points 1084 | * per the Web Components spec 1085 | */ 1086 | CompilerProto.resolveContent = function () { 1087 | 1088 | var outlets = slice.call(this.el.getElementsByTagName('content')), 1089 | raw = this.rawContent, 1090 | outlet, select, i, j, main 1091 | 1092 | i = outlets.length 1093 | if (i) { 1094 | // first pass, collect corresponding content 1095 | // for each outlet. 1096 | while (i--) { 1097 | outlet = outlets[i] 1098 | if (raw) { 1099 | select = outlet.getAttribute('select') 1100 | if (select) { // select content 1101 | outlet.content = 1102 | slice.call(raw.querySelectorAll(select)) 1103 | } else { // default content 1104 | main = outlet 1105 | } 1106 | } else { // fallback content 1107 | outlet.content = 1108 | slice.call(outlet.childNodes) 1109 | } 1110 | } 1111 | // second pass, actually insert the contents 1112 | for (i = 0, j = outlets.length; i < j; i++) { 1113 | outlet = outlets[i] 1114 | if (outlet === main) continue 1115 | insert(outlet, outlet.content) 1116 | } 1117 | // finally insert the main content 1118 | if (raw && main) { 1119 | insert(main, slice.call(raw.childNodes)) 1120 | } 1121 | } 1122 | 1123 | function insert (outlet, contents) { 1124 | var parent = outlet.parentNode, 1125 | i = 0, j = contents.length 1126 | for (; i < j; i++) { 1127 | parent.insertBefore(contents[i], outlet) 1128 | } 1129 | parent.removeChild(outlet) 1130 | } 1131 | 1132 | this.rawContent = null 1133 | } 1134 | 1135 | /** 1136 | * Setup observer. 1137 | * The observer listens for get/set/mutate events on all VM 1138 | * values/objects and trigger corresponding binding updates. 1139 | * It also listens for lifecycle hooks. 1140 | */ 1141 | CompilerProto.setupObserver = function () { 1142 | 1143 | var compiler = this, 1144 | bindings = compiler.bindings, 1145 | options = compiler.options, 1146 | observer = compiler.observer = new Emitter(compiler.vm) 1147 | 1148 | // a hash to hold event proxies for each root level key 1149 | // so they can be referenced and removed later 1150 | observer.proxies = {} 1151 | 1152 | // add own listeners which trigger binding updates 1153 | observer 1154 | .on('get', onGet) 1155 | .on('set', onSet) 1156 | .on('mutate', onSet) 1157 | 1158 | // register hooks 1159 | var i = hooks.length, j, hook, fns 1160 | while (i--) { 1161 | hook = hooks[i] 1162 | fns = options[hook] 1163 | if (Array.isArray(fns)) { 1164 | j = fns.length 1165 | // since hooks were merged with child at head, 1166 | // we loop reversely. 1167 | while (j--) { 1168 | registerHook(hook, fns[j]) 1169 | } 1170 | } else if (fns) { 1171 | registerHook(hook, fns) 1172 | } 1173 | } 1174 | 1175 | // broadcast attached/detached hooks 1176 | observer 1177 | .on('hook:attached', function () { 1178 | broadcast(1) 1179 | }) 1180 | .on('hook:detached', function () { 1181 | broadcast(0) 1182 | }) 1183 | 1184 | function onGet (key) { 1185 | check(key) 1186 | DepsParser.catcher.emit('get', bindings[key]) 1187 | } 1188 | 1189 | function onSet (key, val, mutation) { 1190 | observer.emit('change:' + key, val, mutation) 1191 | check(key) 1192 | bindings[key].update(val) 1193 | } 1194 | 1195 | function registerHook (hook, fn) { 1196 | observer.on('hook:' + hook, function () { 1197 | fn.call(compiler.vm) 1198 | }) 1199 | } 1200 | 1201 | function broadcast (event) { 1202 | var children = compiler.children 1203 | if (children) { 1204 | var child, i = children.length 1205 | while (i--) { 1206 | child = children[i] 1207 | if (child.el.parentNode) { 1208 | event = 'hook:' + (event ? 'attached' : 'detached') 1209 | child.observer.emit(event) 1210 | child.emitter.emit(event) 1211 | } 1212 | } 1213 | } 1214 | } 1215 | 1216 | function check (key) { 1217 | if (!bindings[key]) { 1218 | compiler.createBinding(key) 1219 | } 1220 | } 1221 | } 1222 | 1223 | CompilerProto.observeData = function (data) { 1224 | 1225 | var compiler = this, 1226 | observer = compiler.observer 1227 | 1228 | // recursively observe nested properties 1229 | Observer.observe(data, '', observer) 1230 | 1231 | // also create binding for top level $data 1232 | // so it can be used in templates too 1233 | var $dataBinding = compiler.bindings['$data'] = new Binding(compiler, '$data') 1234 | $dataBinding.update(data) 1235 | 1236 | // allow $data to be swapped 1237 | def(compiler.vm, '$data', { 1238 | get: function () { 1239 | compiler.observer.emit('get', '$data') 1240 | return compiler.data 1241 | }, 1242 | set: function (newData) { 1243 | var oldData = compiler.data 1244 | Observer.unobserve(oldData, '', observer) 1245 | compiler.data = newData 1246 | Observer.copyPaths(newData, oldData) 1247 | Observer.observe(newData, '', observer) 1248 | update() 1249 | } 1250 | }) 1251 | 1252 | // emit $data change on all changes 1253 | observer 1254 | .on('set', onSet) 1255 | .on('mutate', onSet) 1256 | 1257 | function onSet (key) { 1258 | if (key !== '$data') update() 1259 | } 1260 | 1261 | function update () { 1262 | $dataBinding.update(compiler.data) 1263 | observer.emit('change:$data', compiler.data) 1264 | } 1265 | } 1266 | 1267 | /** 1268 | * Compile a DOM node (recursive) 1269 | */ 1270 | CompilerProto.compile = function (node, root) { 1271 | var nodeType = node.nodeType 1272 | if (nodeType === 1 && node.tagName !== 'SCRIPT') { // a normal node 1273 | this.compileElement(node, root) 1274 | } else if (nodeType === 3 && config.interpolate) { 1275 | this.compileTextNode(node) 1276 | } 1277 | } 1278 | 1279 | /** 1280 | * Check for a priority directive 1281 | * If it is present and valid, return true to skip the rest 1282 | */ 1283 | CompilerProto.checkPriorityDir = function (dirname, node, root) { 1284 | var expression, directive, Ctor 1285 | if ( 1286 | dirname === 'component' && 1287 | root !== true && 1288 | (Ctor = this.resolveComponent(node, undefined, true)) 1289 | ) { 1290 | directive = this.parseDirective(dirname, '', node) 1291 | directive.Ctor = Ctor 1292 | } else { 1293 | expression = utils.attr(node, dirname) 1294 | directive = expression && this.parseDirective(dirname, expression, node) 1295 | } 1296 | if (directive) { 1297 | if (root === true) { 1298 | utils.warn( 1299 | 'Directive v-' + dirname + ' cannot be used on an already instantiated ' + 1300 | 'VM\'s root node. Use it from the parent\'s template instead.' 1301 | ) 1302 | return 1303 | } 1304 | this.deferred.push(directive) 1305 | return true 1306 | } 1307 | } 1308 | 1309 | /** 1310 | * Compile normal directives on a node 1311 | */ 1312 | CompilerProto.compileElement = function (node, root) { 1313 | 1314 | // textarea is pretty annoying 1315 | // because its value creates childNodes which 1316 | // we don't want to compile. 1317 | if (node.tagName === 'TEXTAREA' && node.value) { 1318 | node.value = this.eval(node.value) 1319 | } 1320 | 1321 | // only compile if this element has attributes 1322 | // or its tagName contains a hyphen (which means it could 1323 | // potentially be a custom element) 1324 | if (node.hasAttributes() || node.tagName.indexOf('-') > -1) { 1325 | 1326 | // skip anything with v-pre 1327 | if (utils.attr(node, 'pre') !== null) { 1328 | return 1329 | } 1330 | 1331 | var i, l, j, k 1332 | 1333 | // check priority directives. 1334 | // if any of them are present, it will take over the node with a childVM 1335 | // so we can skip the rest 1336 | for (i = 0, l = priorityDirectives.length; i < l; i++) { 1337 | if (this.checkPriorityDir(priorityDirectives[i], node, root)) { 1338 | return 1339 | } 1340 | } 1341 | 1342 | // check transition & animation properties 1343 | node.vue_trans = utils.attr(node, 'transition') 1344 | node.vue_anim = utils.attr(node, 'animation') 1345 | node.vue_effect = this.eval(utils.attr(node, 'effect')) 1346 | 1347 | var prefix = config.prefix + '-', 1348 | attrs = slice.call(node.attributes), 1349 | params = this.options.paramAttributes, 1350 | attr, isDirective, exp, directives, directive, dirname 1351 | 1352 | for (i = 0, l = attrs.length; i < l; i++) { 1353 | 1354 | attr = attrs[i] 1355 | isDirective = false 1356 | 1357 | if (attr.name.indexOf(prefix) === 0) { 1358 | // a directive - split, parse and bind it. 1359 | isDirective = true 1360 | dirname = attr.name.slice(prefix.length) 1361 | // build with multiple: true 1362 | directives = this.parseDirective(dirname, attr.value, node, true) 1363 | // loop through clauses (separated by ",") 1364 | // inside each attribute 1365 | for (j = 0, k = directives.length; j < k; j++) { 1366 | directive = directives[j] 1367 | if (dirname === 'with') { 1368 | this.bindDirective(directive, this.parent) 1369 | } else { 1370 | this.bindDirective(directive) 1371 | } 1372 | } 1373 | } else if (config.interpolate) { 1374 | // non directive attribute, check interpolation tags 1375 | exp = TextParser.parseAttr(attr.value) 1376 | if (exp) { 1377 | directive = this.parseDirective('attr', attr.name + ':' + exp, node) 1378 | if (params && params.indexOf(attr.name) > -1) { 1379 | // a param attribute... we should use the parent binding 1380 | // to avoid circular updates like size={{size}} 1381 | this.bindDirective(directive, this.parent) 1382 | } else { 1383 | this.bindDirective(directive) 1384 | } 1385 | } 1386 | } 1387 | 1388 | if (isDirective && dirname !== 'cloak') { 1389 | node.removeAttribute(attr.name) 1390 | } 1391 | } 1392 | 1393 | } 1394 | 1395 | // recursively compile childNodes 1396 | if (node.hasChildNodes()) { 1397 | slice.call(node.childNodes).forEach(this.compile, this) 1398 | } 1399 | } 1400 | 1401 | /** 1402 | * Compile a text node 1403 | */ 1404 | CompilerProto.compileTextNode = function (node) { 1405 | 1406 | var tokens = TextParser.parse(node.nodeValue) 1407 | if (!tokens) return 1408 | var el, token, directive 1409 | 1410 | for (var i = 0, l = tokens.length; i < l; i++) { 1411 | 1412 | token = tokens[i] 1413 | directive = null 1414 | 1415 | if (token.key) { // a binding 1416 | if (token.key.charAt(0) === '>') { // a partial 1417 | el = document.createComment('ref') 1418 | directive = this.parseDirective('partial', token.key.slice(1), el) 1419 | } else { 1420 | if (!token.html) { // text binding 1421 | el = document.createTextNode('') 1422 | directive = this.parseDirective('text', token.key, el) 1423 | } else { // html binding 1424 | el = document.createComment(config.prefix + '-html') 1425 | directive = this.parseDirective('html', token.key, el) 1426 | } 1427 | } 1428 | } else { // a plain string 1429 | el = document.createTextNode(token) 1430 | } 1431 | 1432 | // insert node 1433 | node.parentNode.insertBefore(el, node) 1434 | // bind directive 1435 | this.bindDirective(directive) 1436 | 1437 | } 1438 | node.parentNode.removeChild(node) 1439 | } 1440 | 1441 | /** 1442 | * Parse a directive name/value pair into one or more 1443 | * directive instances 1444 | */ 1445 | CompilerProto.parseDirective = function (name, value, el, multiple) { 1446 | var compiler = this, 1447 | definition = compiler.getOption('directives', name) 1448 | if (definition) { 1449 | // parse into AST-like objects 1450 | var asts = Directive.parse(value) 1451 | return multiple 1452 | ? asts.map(build) 1453 | : build(asts[0]) 1454 | } 1455 | function build (ast) { 1456 | return new Directive(name, ast, definition, compiler, el) 1457 | } 1458 | } 1459 | 1460 | /** 1461 | * Add a directive instance to the correct binding & viewmodel 1462 | */ 1463 | CompilerProto.bindDirective = function (directive, bindingOwner) { 1464 | 1465 | if (!directive) return 1466 | 1467 | // keep track of it so we can unbind() later 1468 | this.dirs.push(directive) 1469 | 1470 | // for empty or literal directives, simply call its bind() 1471 | // and we're done. 1472 | if (directive.isEmpty || directive.isLiteral) { 1473 | if (directive.bind) directive.bind() 1474 | return 1475 | } 1476 | 1477 | // otherwise, we got more work to do... 1478 | var binding, 1479 | compiler = bindingOwner || this, 1480 | key = directive.key 1481 | 1482 | if (directive.isExp) { 1483 | // expression bindings are always created on current compiler 1484 | binding = compiler.createBinding(key, directive) 1485 | } else { 1486 | // recursively locate which compiler owns the binding 1487 | while (compiler) { 1488 | if (compiler.hasKey(key)) { 1489 | break 1490 | } else { 1491 | compiler = compiler.parent 1492 | } 1493 | } 1494 | compiler = compiler || this 1495 | binding = compiler.bindings[key] || compiler.createBinding(key) 1496 | } 1497 | binding.dirs.push(directive) 1498 | directive.binding = binding 1499 | 1500 | var value = binding.val() 1501 | // invoke bind hook if exists 1502 | if (directive.bind) { 1503 | directive.bind(value) 1504 | } 1505 | // set initial value 1506 | directive.update(value, true) 1507 | } 1508 | 1509 | /** 1510 | * Create binding and attach getter/setter for a key to the viewmodel object 1511 | */ 1512 | CompilerProto.createBinding = function (key, directive) { 1513 | 1514 | utils.log(' created binding: ' + key) 1515 | 1516 | var compiler = this, 1517 | methods = compiler.options.methods, 1518 | isExp = directive && directive.isExp, 1519 | isFn = (directive && directive.isFn) || (methods && methods[key]), 1520 | bindings = compiler.bindings, 1521 | computed = compiler.options.computed, 1522 | binding = new Binding(compiler, key, isExp, isFn) 1523 | 1524 | if (isExp) { 1525 | // expression bindings are anonymous 1526 | compiler.defineExp(key, binding, directive) 1527 | } else if (isFn) { 1528 | bindings[key] = binding 1529 | binding.value = compiler.vm[key] = methods[key] 1530 | } else { 1531 | bindings[key] = binding 1532 | if (binding.root) { 1533 | // this is a root level binding. we need to define getter/setters for it. 1534 | if (computed && computed[key]) { 1535 | // computed property 1536 | compiler.defineComputed(key, binding, computed[key]) 1537 | } else if (key.charAt(0) !== '$') { 1538 | // normal property 1539 | compiler.defineProp(key, binding) 1540 | } else { 1541 | compiler.defineMeta(key, binding) 1542 | } 1543 | } else if (computed && computed[utils.baseKey(key)]) { 1544 | // nested path on computed property 1545 | compiler.defineExp(key, binding) 1546 | } else { 1547 | // ensure path in data so that computed properties that 1548 | // access the path don't throw an error and can collect 1549 | // dependencies 1550 | Observer.ensurePath(compiler.data, key) 1551 | var parentKey = key.slice(0, key.lastIndexOf('.')) 1552 | if (!bindings[parentKey]) { 1553 | // this is a nested value binding, but the binding for its parent 1554 | // has not been created yet. We better create that one too. 1555 | compiler.createBinding(parentKey) 1556 | } 1557 | } 1558 | } 1559 | return binding 1560 | } 1561 | 1562 | /** 1563 | * Define the getter/setter for a root-level property on the VM 1564 | * and observe the initial value 1565 | */ 1566 | CompilerProto.defineProp = function (key, binding) { 1567 | var compiler = this, 1568 | data = compiler.data, 1569 | ob = data.__emitter__ 1570 | 1571 | // make sure the key is present in data 1572 | // so it can be observed 1573 | if (!(hasOwn.call(data, key))) { 1574 | data[key] = undefined 1575 | } 1576 | 1577 | // if the data object is already observed, but the key 1578 | // is not observed, we need to add it to the observed keys. 1579 | if (ob && !(hasOwn.call(ob.values, key))) { 1580 | Observer.convertKey(data, key) 1581 | } 1582 | 1583 | binding.value = data[key] 1584 | 1585 | def(compiler.vm, key, { 1586 | get: function () { 1587 | return compiler.data[key] 1588 | }, 1589 | set: function (val) { 1590 | compiler.data[key] = val 1591 | } 1592 | }) 1593 | } 1594 | 1595 | /** 1596 | * Define a meta property, e.g. $index or $key, 1597 | * which is bindable but only accessible on the VM, 1598 | * not in the data. 1599 | */ 1600 | CompilerProto.defineMeta = function (key, binding) { 1601 | var ob = this.observer 1602 | binding.value = this.data[key] 1603 | delete this.data[key] 1604 | def(this.vm, key, { 1605 | get: function () { 1606 | if (Observer.shouldGet) ob.emit('get', key) 1607 | return binding.value 1608 | }, 1609 | set: function (val) { 1610 | ob.emit('set', key, val) 1611 | } 1612 | }) 1613 | } 1614 | 1615 | /** 1616 | * Define an expression binding, which is essentially 1617 | * an anonymous computed property 1618 | */ 1619 | CompilerProto.defineExp = function (key, binding, directive) { 1620 | var computedKey = directive && directive.computedKey, 1621 | exp = computedKey ? directive.expression : key, 1622 | getter = this.expCache[exp] 1623 | if (!getter) { 1624 | getter = this.expCache[exp] = ExpParser.parse(computedKey || key, this) 1625 | } 1626 | if (getter) { 1627 | this.markComputed(binding, getter) 1628 | } 1629 | } 1630 | 1631 | /** 1632 | * Define a computed property on the VM 1633 | */ 1634 | CompilerProto.defineComputed = function (key, binding, value) { 1635 | this.markComputed(binding, value) 1636 | def(this.vm, key, { 1637 | get: binding.value.$get, 1638 | set: binding.value.$set 1639 | }) 1640 | } 1641 | 1642 | /** 1643 | * Process a computed property binding 1644 | * so its getter/setter are bound to proper context 1645 | */ 1646 | CompilerProto.markComputed = function (binding, value) { 1647 | binding.isComputed = true 1648 | // bind the accessors to the vm 1649 | if (binding.isFn) { 1650 | binding.value = value 1651 | } else { 1652 | if (typeof value === 'function') { 1653 | value = { $get: value } 1654 | } 1655 | binding.value = { 1656 | $get: utils.bind(value.$get, this.vm), 1657 | $set: value.$set 1658 | ? utils.bind(value.$set, this.vm) 1659 | : undefined 1660 | } 1661 | } 1662 | // keep track for dep parsing later 1663 | this.computed.push(binding) 1664 | } 1665 | 1666 | /** 1667 | * Retrive an option from the compiler 1668 | */ 1669 | CompilerProto.getOption = function (type, id, silent) { 1670 | var opts = this.options, 1671 | parent = this.parent, 1672 | globalAssets = config.globalAssets, 1673 | res = (opts[type] && opts[type][id]) || ( 1674 | parent 1675 | ? parent.getOption(type, id, silent) 1676 | : globalAssets[type] && globalAssets[type][id] 1677 | ) 1678 | if (!res && !silent && typeof id === 'string') { 1679 | utils.warn('Unknown ' + type.slice(0, -1) + ': ' + id) 1680 | } 1681 | return res 1682 | } 1683 | 1684 | /** 1685 | * Emit lifecycle events to trigger hooks 1686 | */ 1687 | CompilerProto.execHook = function (event) { 1688 | event = 'hook:' + event 1689 | this.observer.emit(event) 1690 | this.emitter.emit(event) 1691 | } 1692 | 1693 | /** 1694 | * Check if a compiler's data contains a keypath 1695 | */ 1696 | CompilerProto.hasKey = function (key) { 1697 | var baseKey = utils.baseKey(key) 1698 | return hasOwn.call(this.data, baseKey) || 1699 | hasOwn.call(this.vm, baseKey) 1700 | } 1701 | 1702 | /** 1703 | * Do a one-time eval of a string that potentially 1704 | * includes bindings. It accepts additional raw data 1705 | * because we need to dynamically resolve v-component 1706 | * before a childVM is even compiled... 1707 | */ 1708 | CompilerProto.eval = function (exp, data) { 1709 | var parsed = TextParser.parseAttr(exp) 1710 | return parsed 1711 | ? ExpParser.eval(parsed, this, data) 1712 | : exp 1713 | } 1714 | 1715 | /** 1716 | * Resolve a Component constructor for an element 1717 | * with the data to be used 1718 | */ 1719 | CompilerProto.resolveComponent = function (node, data, test) { 1720 | 1721 | // late require to avoid circular deps 1722 | ViewModel = ViewModel || require('./viewmodel') 1723 | 1724 | var exp = utils.attr(node, 'component'), 1725 | tagName = node.tagName, 1726 | id = this.eval(exp, data), 1727 | tagId = (tagName.indexOf('-') > 0 && tagName.toLowerCase()), 1728 | Ctor = this.getOption('components', id || tagId, true) 1729 | 1730 | if (id && !Ctor) { 1731 | utils.warn('Unknown component: ' + id) 1732 | } 1733 | 1734 | return test 1735 | ? exp === '' 1736 | ? ViewModel 1737 | : Ctor 1738 | : Ctor || ViewModel 1739 | } 1740 | 1741 | /** 1742 | * Unbind and remove element 1743 | */ 1744 | CompilerProto.destroy = function () { 1745 | 1746 | // avoid being called more than once 1747 | // this is irreversible! 1748 | if (this.destroyed) return 1749 | 1750 | var compiler = this, 1751 | i, j, key, dir, dirs, binding, 1752 | vm = compiler.vm, 1753 | el = compiler.el, 1754 | directives = compiler.dirs, 1755 | computed = compiler.computed, 1756 | bindings = compiler.bindings, 1757 | children = compiler.children, 1758 | parent = compiler.parent 1759 | 1760 | compiler.execHook('beforeDestroy') 1761 | 1762 | // unobserve data 1763 | Observer.unobserve(compiler.data, '', compiler.observer) 1764 | 1765 | // unbind all direcitves 1766 | i = directives.length 1767 | while (i--) { 1768 | dir = directives[i] 1769 | // if this directive is an instance of an external binding 1770 | // e.g. a directive that refers to a variable on the parent VM 1771 | // we need to remove it from that binding's directives 1772 | // * empty and literal bindings do not have binding. 1773 | if (dir.binding && dir.binding.compiler !== compiler) { 1774 | dirs = dir.binding.dirs 1775 | if (dirs) { 1776 | j = dirs.indexOf(dir) 1777 | if (j > -1) dirs.splice(j, 1) 1778 | } 1779 | } 1780 | dir.unbind() 1781 | } 1782 | 1783 | // unbind all computed, anonymous bindings 1784 | i = computed.length 1785 | while (i--) { 1786 | computed[i].unbind() 1787 | } 1788 | 1789 | // unbind all keypath bindings 1790 | for (key in bindings) { 1791 | binding = bindings[key] 1792 | if (binding) { 1793 | binding.unbind() 1794 | } 1795 | } 1796 | 1797 | // destroy all children 1798 | i = children.length 1799 | while (i--) { 1800 | children[i].destroy() 1801 | } 1802 | 1803 | // remove self from parent 1804 | if (parent) { 1805 | j = parent.children.indexOf(compiler) 1806 | if (j > -1) parent.children.splice(j, 1) 1807 | } 1808 | 1809 | // finally remove dom element 1810 | if (el === document.body) { 1811 | el.innerHTML = '' 1812 | } else { 1813 | vm.$remove() 1814 | } 1815 | el.vue_vm = null 1816 | 1817 | compiler.destroyed = true 1818 | // emit destroy hook 1819 | compiler.execHook('afterDestroy') 1820 | 1821 | // finally, unregister all listeners 1822 | compiler.observer.off() 1823 | compiler.emitter.off() 1824 | } 1825 | 1826 | // Helpers -------------------------------------------------------------------- 1827 | 1828 | /** 1829 | * shorthand for getting root compiler 1830 | */ 1831 | function getRoot (compiler) { 1832 | while (compiler.parent) { 1833 | compiler = compiler.parent 1834 | } 1835 | return compiler 1836 | } 1837 | 1838 | module.exports = Compiler 1839 | }); 1840 | require.register("vue/src/viewmodel.js", function(exports, require, module){ 1841 | var Compiler = require('./compiler'), 1842 | utils = require('./utils'), 1843 | transition = require('./transition'), 1844 | Batcher = require('./batcher'), 1845 | slice = [].slice, 1846 | def = utils.defProtected, 1847 | nextTick = utils.nextTick, 1848 | 1849 | // batch $watch callbacks 1850 | watcherBatcher = new Batcher(), 1851 | watcherId = 1 1852 | 1853 | /** 1854 | * ViewModel exposed to the user that holds data, 1855 | * computed properties, event handlers 1856 | * and a few reserved methods 1857 | */ 1858 | function ViewModel (options) { 1859 | // just compile. options are passed directly to compiler 1860 | new Compiler(this, options) 1861 | } 1862 | 1863 | // All VM prototype methods are inenumerable 1864 | // so it can be stringified/looped through as raw data 1865 | var VMProto = ViewModel.prototype 1866 | 1867 | /** 1868 | * Convenience function to get a value from 1869 | * a keypath 1870 | */ 1871 | def(VMProto, '$get', function (key) { 1872 | var val = utils.get(this, key) 1873 | return val === undefined && this.$parent 1874 | ? this.$parent.$get(key) 1875 | : val 1876 | }) 1877 | 1878 | /** 1879 | * Convenience function to set an actual nested value 1880 | * from a flat key string. Used in directives. 1881 | */ 1882 | def(VMProto, '$set', function (key, value) { 1883 | utils.set(this, key, value) 1884 | }) 1885 | 1886 | /** 1887 | * watch a key on the viewmodel for changes 1888 | * fire callback with new value 1889 | */ 1890 | def(VMProto, '$watch', function (key, callback) { 1891 | // save a unique id for each watcher 1892 | var id = watcherId++, 1893 | self = this 1894 | function on () { 1895 | var args = slice.call(arguments) 1896 | watcherBatcher.push({ 1897 | id: id, 1898 | override: true, 1899 | execute: function () { 1900 | callback.apply(self, args) 1901 | } 1902 | }) 1903 | } 1904 | callback._fn = on 1905 | self.$compiler.observer.on('change:' + key, on) 1906 | }) 1907 | 1908 | /** 1909 | * unwatch a key 1910 | */ 1911 | def(VMProto, '$unwatch', function (key, callback) { 1912 | // workaround here 1913 | // since the emitter module checks callback existence 1914 | // by checking the length of arguments 1915 | var args = ['change:' + key], 1916 | ob = this.$compiler.observer 1917 | if (callback) args.push(callback._fn) 1918 | ob.off.apply(ob, args) 1919 | }) 1920 | 1921 | /** 1922 | * unbind everything, remove everything 1923 | */ 1924 | def(VMProto, '$destroy', function () { 1925 | this.$compiler.destroy() 1926 | }) 1927 | 1928 | /** 1929 | * broadcast an event to all child VMs recursively. 1930 | */ 1931 | def(VMProto, '$broadcast', function () { 1932 | var children = this.$compiler.children, 1933 | i = children.length, 1934 | child 1935 | while (i--) { 1936 | child = children[i] 1937 | child.emitter.emit.apply(child.emitter, arguments) 1938 | child.vm.$broadcast.apply(child.vm, arguments) 1939 | } 1940 | }) 1941 | 1942 | /** 1943 | * emit an event that propagates all the way up to parent VMs. 1944 | */ 1945 | def(VMProto, '$dispatch', function () { 1946 | var compiler = this.$compiler, 1947 | emitter = compiler.emitter, 1948 | parent = compiler.parent 1949 | emitter.emit.apply(emitter, arguments) 1950 | if (parent) { 1951 | parent.vm.$dispatch.apply(parent.vm, arguments) 1952 | } 1953 | }) 1954 | 1955 | /** 1956 | * delegate on/off/once to the compiler's emitter 1957 | */ 1958 | ;['emit', 'on', 'off', 'once'].forEach(function (method) { 1959 | def(VMProto, '$' + method, function () { 1960 | var emitter = this.$compiler.emitter 1961 | emitter[method].apply(emitter, arguments) 1962 | }) 1963 | }) 1964 | 1965 | // DOM convenience methods 1966 | 1967 | def(VMProto, '$appendTo', function (target, cb) { 1968 | target = query(target) 1969 | var el = this.$el 1970 | transition(el, 1, function () { 1971 | target.appendChild(el) 1972 | if (cb) nextTick(cb) 1973 | }, this.$compiler) 1974 | }) 1975 | 1976 | def(VMProto, '$remove', function (cb) { 1977 | var el = this.$el 1978 | transition(el, -1, function () { 1979 | if (el.parentNode) { 1980 | el.parentNode.removeChild(el) 1981 | } 1982 | if (cb) nextTick(cb) 1983 | }, this.$compiler) 1984 | }) 1985 | 1986 | def(VMProto, '$before', function (target, cb) { 1987 | target = query(target) 1988 | var el = this.$el 1989 | transition(el, 1, function () { 1990 | target.parentNode.insertBefore(el, target) 1991 | if (cb) nextTick(cb) 1992 | }, this.$compiler) 1993 | }) 1994 | 1995 | def(VMProto, '$after', function (target, cb) { 1996 | target = query(target) 1997 | var el = this.$el 1998 | transition(el, 1, function () { 1999 | if (target.nextSibling) { 2000 | target.parentNode.insertBefore(el, target.nextSibling) 2001 | } else { 2002 | target.parentNode.appendChild(el) 2003 | } 2004 | if (cb) nextTick(cb) 2005 | }, this.$compiler) 2006 | }) 2007 | 2008 | function query (el) { 2009 | return typeof el === 'string' 2010 | ? document.querySelector(el) 2011 | : el 2012 | } 2013 | 2014 | module.exports = ViewModel 2015 | }); 2016 | require.register("vue/src/binding.js", function(exports, require, module){ 2017 | var Batcher = require('./batcher'), 2018 | bindingBatcher = new Batcher(), 2019 | bindingId = 1 2020 | 2021 | /** 2022 | * Binding class. 2023 | * 2024 | * each property on the viewmodel has one corresponding Binding object 2025 | * which has multiple directive instances on the DOM 2026 | * and multiple computed property dependents 2027 | */ 2028 | function Binding (compiler, key, isExp, isFn) { 2029 | this.id = bindingId++ 2030 | this.value = undefined 2031 | this.isExp = !!isExp 2032 | this.isFn = isFn 2033 | this.root = !this.isExp && key.indexOf('.') === -1 2034 | this.compiler = compiler 2035 | this.key = key 2036 | this.dirs = [] 2037 | this.subs = [] 2038 | this.deps = [] 2039 | this.unbound = false 2040 | } 2041 | 2042 | var BindingProto = Binding.prototype 2043 | 2044 | /** 2045 | * Update value and queue instance updates. 2046 | */ 2047 | BindingProto.update = function (value) { 2048 | if (!this.isComputed || this.isFn) { 2049 | this.value = value 2050 | } 2051 | if (this.dirs.length || this.subs.length) { 2052 | var self = this 2053 | bindingBatcher.push({ 2054 | id: this.id, 2055 | execute: function () { 2056 | if (!self.unbound) { 2057 | self._update() 2058 | } 2059 | } 2060 | }) 2061 | } 2062 | } 2063 | 2064 | /** 2065 | * Actually update the directives. 2066 | */ 2067 | BindingProto._update = function () { 2068 | var i = this.dirs.length, 2069 | value = this.val() 2070 | while (i--) { 2071 | this.dirs[i].update(value) 2072 | } 2073 | this.pub() 2074 | } 2075 | 2076 | /** 2077 | * Return the valuated value regardless 2078 | * of whether it is computed or not 2079 | */ 2080 | BindingProto.val = function () { 2081 | return this.isComputed && !this.isFn 2082 | ? this.value.$get() 2083 | : this.value 2084 | } 2085 | 2086 | /** 2087 | * Notify computed properties that depend on this binding 2088 | * to update themselves 2089 | */ 2090 | BindingProto.pub = function () { 2091 | var i = this.subs.length 2092 | while (i--) { 2093 | this.subs[i].update() 2094 | } 2095 | } 2096 | 2097 | /** 2098 | * Unbind the binding, remove itself from all of its dependencies 2099 | */ 2100 | BindingProto.unbind = function () { 2101 | // Indicate this has been unbound. 2102 | // It's possible this binding will be in 2103 | // the batcher's flush queue when its owner 2104 | // compiler has already been destroyed. 2105 | this.unbound = true 2106 | var i = this.dirs.length 2107 | while (i--) { 2108 | this.dirs[i].unbind() 2109 | } 2110 | i = this.deps.length 2111 | var subs 2112 | while (i--) { 2113 | subs = this.deps[i].subs 2114 | var j = subs.indexOf(this) 2115 | if (j > -1) subs.splice(j, 1) 2116 | } 2117 | } 2118 | 2119 | module.exports = Binding 2120 | }); 2121 | require.register("vue/src/observer.js", function(exports, require, module){ 2122 | /* jshint proto:true */ 2123 | 2124 | var Emitter = require('./emitter'), 2125 | utils = require('./utils'), 2126 | // cache methods 2127 | def = utils.defProtected, 2128 | isObject = utils.isObject, 2129 | isArray = Array.isArray, 2130 | hasOwn = ({}).hasOwnProperty, 2131 | oDef = Object.defineProperty, 2132 | slice = [].slice, 2133 | // fix for IE + __proto__ problem 2134 | // define methods as inenumerable if __proto__ is present, 2135 | // otherwise enumerable so we can loop through and manually 2136 | // attach to array instances 2137 | hasProto = ({}).__proto__ 2138 | 2139 | // Array Mutation Handlers & Augmentations ------------------------------------ 2140 | 2141 | // The proxy prototype to replace the __proto__ of 2142 | // an observed array 2143 | var ArrayProxy = Object.create(Array.prototype) 2144 | 2145 | // intercept mutation methods 2146 | ;[ 2147 | 'push', 2148 | 'pop', 2149 | 'shift', 2150 | 'unshift', 2151 | 'splice', 2152 | 'sort', 2153 | 'reverse' 2154 | ].forEach(watchMutation) 2155 | 2156 | // Augment the ArrayProxy with convenience methods 2157 | def(ArrayProxy, '$set', function (index, data) { 2158 | return this.splice(index, 1, data)[0] 2159 | }, !hasProto) 2160 | 2161 | def(ArrayProxy, '$remove', function (index) { 2162 | if (typeof index !== 'number') { 2163 | index = this.indexOf(index) 2164 | } 2165 | if (index > -1) { 2166 | return this.splice(index, 1)[0] 2167 | } 2168 | }, !hasProto) 2169 | 2170 | /** 2171 | * Intercep a mutation event so we can emit the mutation info. 2172 | * we also analyze what elements are added/removed and link/unlink 2173 | * them with the parent Array. 2174 | */ 2175 | function watchMutation (method) { 2176 | def(ArrayProxy, method, function () { 2177 | 2178 | var args = slice.call(arguments), 2179 | result = Array.prototype[method].apply(this, args), 2180 | inserted, removed 2181 | 2182 | // determine new / removed elements 2183 | if (method === 'push' || method === 'unshift') { 2184 | inserted = args 2185 | } else if (method === 'pop' || method === 'shift') { 2186 | removed = [result] 2187 | } else if (method === 'splice') { 2188 | inserted = args.slice(2) 2189 | removed = result 2190 | } 2191 | 2192 | // link & unlink 2193 | linkArrayElements(this, inserted) 2194 | unlinkArrayElements(this, removed) 2195 | 2196 | // emit the mutation event 2197 | this.__emitter__.emit('mutate', '', this, { 2198 | method : method, 2199 | args : args, 2200 | result : result, 2201 | inserted : inserted, 2202 | removed : removed 2203 | }) 2204 | 2205 | return result 2206 | 2207 | }, !hasProto) 2208 | } 2209 | 2210 | /** 2211 | * Link new elements to an Array, so when they change 2212 | * and emit events, the owner Array can be notified. 2213 | */ 2214 | function linkArrayElements (arr, items) { 2215 | if (items) { 2216 | var i = items.length, item, owners 2217 | while (i--) { 2218 | item = items[i] 2219 | if (isWatchable(item)) { 2220 | // if object is not converted for observing 2221 | // convert it... 2222 | if (!item.__emitter__) { 2223 | convert(item) 2224 | watch(item) 2225 | } 2226 | owners = item.__emitter__.owners 2227 | if (owners.indexOf(arr) < 0) { 2228 | owners.push(arr) 2229 | } 2230 | } 2231 | } 2232 | } 2233 | } 2234 | 2235 | /** 2236 | * Unlink removed elements from the ex-owner Array. 2237 | */ 2238 | function unlinkArrayElements (arr, items) { 2239 | if (items) { 2240 | var i = items.length, item 2241 | while (i--) { 2242 | item = items[i] 2243 | if (item && item.__emitter__) { 2244 | var owners = item.__emitter__.owners 2245 | if (owners) owners.splice(owners.indexOf(arr)) 2246 | } 2247 | } 2248 | } 2249 | } 2250 | 2251 | // Object add/delete key augmentation ----------------------------------------- 2252 | 2253 | var ObjProxy = Object.create(Object.prototype) 2254 | 2255 | def(ObjProxy, '$add', function (key, val) { 2256 | if (hasOwn.call(this, key)) return 2257 | this[key] = val 2258 | convertKey(this, key) 2259 | // emit a propagating set event 2260 | this.__emitter__.emit('set', key, val, true) 2261 | }, !hasProto) 2262 | 2263 | def(ObjProxy, '$delete', function (key) { 2264 | if (!(hasOwn.call(this, key))) return 2265 | // trigger set events 2266 | this[key] = undefined 2267 | delete this[key] 2268 | this.__emitter__.emit('delete', key) 2269 | }, !hasProto) 2270 | 2271 | // Watch Helpers -------------------------------------------------------------- 2272 | 2273 | /** 2274 | * Check if a value is watchable 2275 | */ 2276 | function isWatchable (obj) { 2277 | return typeof obj === 'object' && obj && !obj.$compiler 2278 | } 2279 | 2280 | /** 2281 | * Convert an Object/Array to give it a change emitter. 2282 | */ 2283 | function convert (obj) { 2284 | if (obj.__emitter__) return true 2285 | var emitter = new Emitter() 2286 | def(obj, '__emitter__', emitter) 2287 | emitter 2288 | .on('set', function (key, val, propagate) { 2289 | if (propagate) propagateChange(obj) 2290 | }) 2291 | .on('mutate', function () { 2292 | propagateChange(obj) 2293 | }) 2294 | emitter.values = utils.hash() 2295 | emitter.owners = [] 2296 | return false 2297 | } 2298 | 2299 | /** 2300 | * Propagate an array element's change to its owner arrays 2301 | */ 2302 | function propagateChange (obj) { 2303 | var owners = obj.__emitter__.owners, 2304 | i = owners.length 2305 | while (i--) { 2306 | owners[i].__emitter__.emit('set', '', '', true) 2307 | } 2308 | } 2309 | 2310 | /** 2311 | * Watch target based on its type 2312 | */ 2313 | function watch (obj) { 2314 | if (isArray(obj)) { 2315 | watchArray(obj) 2316 | } else { 2317 | watchObject(obj) 2318 | } 2319 | } 2320 | 2321 | /** 2322 | * Augment target objects with modified 2323 | * methods 2324 | */ 2325 | function augment (target, src) { 2326 | if (hasProto) { 2327 | target.__proto__ = src 2328 | } else { 2329 | for (var key in src) { 2330 | def(target, key, src[key]) 2331 | } 2332 | } 2333 | } 2334 | 2335 | /** 2336 | * Watch an Object, recursive. 2337 | */ 2338 | function watchObject (obj) { 2339 | augment(obj, ObjProxy) 2340 | for (var key in obj) { 2341 | convertKey(obj, key) 2342 | } 2343 | } 2344 | 2345 | /** 2346 | * Watch an Array, overload mutation methods 2347 | * and add augmentations by intercepting the prototype chain 2348 | */ 2349 | function watchArray (arr) { 2350 | augment(arr, ArrayProxy) 2351 | linkArrayElements(arr, arr) 2352 | } 2353 | 2354 | /** 2355 | * Define accessors for a property on an Object 2356 | * so it emits get/set events. 2357 | * Then watch the value itself. 2358 | */ 2359 | function convertKey (obj, key) { 2360 | var keyPrefix = key.charAt(0) 2361 | if (keyPrefix === '$' || keyPrefix === '_') { 2362 | return 2363 | } 2364 | // emit set on bind 2365 | // this means when an object is observed it will emit 2366 | // a first batch of set events. 2367 | var emitter = obj.__emitter__, 2368 | values = emitter.values 2369 | 2370 | init(obj[key]) 2371 | 2372 | oDef(obj, key, { 2373 | enumerable: true, 2374 | configurable: true, 2375 | get: function () { 2376 | var value = values[key] 2377 | // only emit get on tip values 2378 | if (pub.shouldGet) { 2379 | emitter.emit('get', key) 2380 | } 2381 | return value 2382 | }, 2383 | set: function (newVal) { 2384 | var oldVal = values[key] 2385 | unobserve(oldVal, key, emitter) 2386 | copyPaths(newVal, oldVal) 2387 | // an immediate property should notify its parent 2388 | // to emit set for itself too 2389 | init(newVal, true) 2390 | } 2391 | }) 2392 | 2393 | function init (val, propagate) { 2394 | values[key] = val 2395 | emitter.emit('set', key, val, propagate) 2396 | if (isArray(val)) { 2397 | emitter.emit('set', key + '.length', val.length, propagate) 2398 | } 2399 | observe(val, key, emitter) 2400 | } 2401 | } 2402 | 2403 | /** 2404 | * When a value that is already converted is 2405 | * observed again by another observer, we can skip 2406 | * the watch conversion and simply emit set event for 2407 | * all of its properties. 2408 | */ 2409 | function emitSet (obj) { 2410 | var emitter = obj && obj.__emitter__ 2411 | if (!emitter) return 2412 | if (isArray(obj)) { 2413 | emitter.emit('set', 'length', obj.length) 2414 | } else { 2415 | var key, val 2416 | for (key in obj) { 2417 | val = obj[key] 2418 | emitter.emit('set', key, val) 2419 | emitSet(val) 2420 | } 2421 | } 2422 | } 2423 | 2424 | /** 2425 | * Make sure all the paths in an old object exists 2426 | * in a new object. 2427 | * So when an object changes, all missing keys will 2428 | * emit a set event with undefined value. 2429 | */ 2430 | function copyPaths (newObj, oldObj) { 2431 | if (!isObject(newObj) || !isObject(oldObj)) { 2432 | return 2433 | } 2434 | var path, oldVal, newVal 2435 | for (path in oldObj) { 2436 | if (!(hasOwn.call(newObj, path))) { 2437 | oldVal = oldObj[path] 2438 | if (isArray(oldVal)) { 2439 | newObj[path] = [] 2440 | } else if (isObject(oldVal)) { 2441 | newVal = newObj[path] = {} 2442 | copyPaths(newVal, oldVal) 2443 | } else { 2444 | newObj[path] = undefined 2445 | } 2446 | } 2447 | } 2448 | } 2449 | 2450 | /** 2451 | * walk along a path and make sure it can be accessed 2452 | * and enumerated in that object 2453 | */ 2454 | function ensurePath (obj, key) { 2455 | var path = key.split('.'), sec 2456 | for (var i = 0, d = path.length - 1; i < d; i++) { 2457 | sec = path[i] 2458 | if (!obj[sec]) { 2459 | obj[sec] = {} 2460 | if (obj.__emitter__) convertKey(obj, sec) 2461 | } 2462 | obj = obj[sec] 2463 | } 2464 | if (isObject(obj)) { 2465 | sec = path[i] 2466 | if (!(hasOwn.call(obj, sec))) { 2467 | obj[sec] = undefined 2468 | if (obj.__emitter__) convertKey(obj, sec) 2469 | } 2470 | } 2471 | } 2472 | 2473 | // Main API Methods ----------------------------------------------------------- 2474 | 2475 | /** 2476 | * Observe an object with a given path, 2477 | * and proxy get/set/mutate events to the provided observer. 2478 | */ 2479 | function observe (obj, rawPath, observer) { 2480 | 2481 | if (!isWatchable(obj)) return 2482 | 2483 | var path = rawPath ? rawPath + '.' : '', 2484 | alreadyConverted = convert(obj), 2485 | emitter = obj.__emitter__ 2486 | 2487 | // setup proxy listeners on the parent observer. 2488 | // we need to keep reference to them so that they 2489 | // can be removed when the object is un-observed. 2490 | observer.proxies = observer.proxies || {} 2491 | var proxies = observer.proxies[path] = { 2492 | get: function (key) { 2493 | observer.emit('get', path + key) 2494 | }, 2495 | set: function (key, val, propagate) { 2496 | if (key) observer.emit('set', path + key, val) 2497 | // also notify observer that the object itself changed 2498 | // but only do so when it's a immediate property. this 2499 | // avoids duplicate event firing. 2500 | if (rawPath && propagate) { 2501 | observer.emit('set', rawPath, obj, true) 2502 | } 2503 | }, 2504 | mutate: function (key, val, mutation) { 2505 | // if the Array is a root value 2506 | // the key will be null 2507 | var fixedPath = key ? path + key : rawPath 2508 | observer.emit('mutate', fixedPath, val, mutation) 2509 | // also emit set for Array's length when it mutates 2510 | var m = mutation.method 2511 | if (m !== 'sort' && m !== 'reverse') { 2512 | observer.emit('set', fixedPath + '.length', val.length) 2513 | } 2514 | } 2515 | } 2516 | 2517 | // attach the listeners to the child observer. 2518 | // now all the events will propagate upwards. 2519 | emitter 2520 | .on('get', proxies.get) 2521 | .on('set', proxies.set) 2522 | .on('mutate', proxies.mutate) 2523 | 2524 | if (alreadyConverted) { 2525 | // for objects that have already been converted, 2526 | // emit set events for everything inside 2527 | emitSet(obj) 2528 | } else { 2529 | watch(obj) 2530 | } 2531 | } 2532 | 2533 | /** 2534 | * Cancel observation, turn off the listeners. 2535 | */ 2536 | function unobserve (obj, path, observer) { 2537 | 2538 | if (!obj || !obj.__emitter__) return 2539 | 2540 | path = path ? path + '.' : '' 2541 | var proxies = observer.proxies[path] 2542 | if (!proxies) return 2543 | 2544 | // turn off listeners 2545 | obj.__emitter__ 2546 | .off('get', proxies.get) 2547 | .off('set', proxies.set) 2548 | .off('mutate', proxies.mutate) 2549 | 2550 | // remove reference 2551 | observer.proxies[path] = null 2552 | } 2553 | 2554 | // Expose API ----------------------------------------------------------------- 2555 | 2556 | var pub = module.exports = { 2557 | 2558 | // whether to emit get events 2559 | // only enabled during dependency parsing 2560 | shouldGet : false, 2561 | 2562 | observe : observe, 2563 | unobserve : unobserve, 2564 | ensurePath : ensurePath, 2565 | copyPaths : copyPaths, 2566 | watch : watch, 2567 | convert : convert, 2568 | convertKey : convertKey 2569 | } 2570 | }); 2571 | require.register("vue/src/directive.js", function(exports, require, module){ 2572 | var dirId = 1, 2573 | ARG_RE = /^[\w\$-]+$/, 2574 | FILTER_TOKEN_RE = /[^\s'"]+|'[^']+'|"[^"]+"/g, 2575 | NESTING_RE = /^\$(parent|root)\./, 2576 | SINGLE_VAR_RE = /^[\w\.$]+$/, 2577 | QUOTE_RE = /"/g 2578 | 2579 | /** 2580 | * Directive class 2581 | * represents a single directive instance in the DOM 2582 | */ 2583 | function Directive (name, ast, definition, compiler, el) { 2584 | 2585 | this.id = dirId++ 2586 | this.name = name 2587 | this.compiler = compiler 2588 | this.vm = compiler.vm 2589 | this.el = el 2590 | this.computeFilters = false 2591 | this.key = ast.key 2592 | this.arg = ast.arg 2593 | this.expression = ast.expression 2594 | 2595 | var isEmpty = this.expression === '' 2596 | 2597 | // mix in properties from the directive definition 2598 | if (typeof definition === 'function') { 2599 | this[isEmpty ? 'bind' : '_update'] = definition 2600 | } else { 2601 | for (var prop in definition) { 2602 | if (prop === 'unbind' || prop === 'update') { 2603 | this['_' + prop] = definition[prop] 2604 | } else { 2605 | this[prop] = definition[prop] 2606 | } 2607 | } 2608 | } 2609 | 2610 | // empty expression, we're done. 2611 | if (isEmpty || this.isEmpty) { 2612 | this.isEmpty = true 2613 | return 2614 | } 2615 | 2616 | this.expression = ( 2617 | this.isLiteral 2618 | ? compiler.eval(this.expression) 2619 | : this.expression 2620 | ).trim() 2621 | 2622 | var filters = ast.filters, 2623 | filter, fn, i, l, computed 2624 | if (filters) { 2625 | this.filters = [] 2626 | for (i = 0, l = filters.length; i < l; i++) { 2627 | filter = filters[i] 2628 | fn = this.compiler.getOption('filters', filter.name) 2629 | if (fn) { 2630 | filter.apply = fn 2631 | this.filters.push(filter) 2632 | if (fn.computed) { 2633 | computed = true 2634 | } 2635 | } 2636 | } 2637 | } 2638 | 2639 | if (!this.filters || !this.filters.length) { 2640 | this.filters = null 2641 | } 2642 | 2643 | if (computed) { 2644 | this.computedKey = Directive.inlineFilters(this.key, this.filters) 2645 | this.filters = null 2646 | } 2647 | 2648 | this.isExp = 2649 | computed || 2650 | !SINGLE_VAR_RE.test(this.key) || 2651 | NESTING_RE.test(this.key) 2652 | 2653 | } 2654 | 2655 | var DirProto = Directive.prototype 2656 | 2657 | /** 2658 | * called when a new value is set 2659 | * for computed properties, this will only be called once 2660 | * during initialization. 2661 | */ 2662 | DirProto.update = function (value, init) { 2663 | if (init || value !== this.value || (value && typeof value === 'object')) { 2664 | this.value = value 2665 | if (this._update) { 2666 | this._update( 2667 | this.filters && !this.computeFilters 2668 | ? this.applyFilters(value) 2669 | : value, 2670 | init 2671 | ) 2672 | } 2673 | } 2674 | } 2675 | 2676 | /** 2677 | * pipe the value through filters 2678 | */ 2679 | DirProto.applyFilters = function (value) { 2680 | var filtered = value, filter 2681 | for (var i = 0, l = this.filters.length; i < l; i++) { 2682 | filter = this.filters[i] 2683 | filtered = filter.apply.apply(this.vm, [filtered].concat(filter.args)) 2684 | } 2685 | return filtered 2686 | } 2687 | 2688 | /** 2689 | * Unbind diretive 2690 | */ 2691 | DirProto.unbind = function () { 2692 | // this can be called before the el is even assigned... 2693 | if (!this.el || !this.vm) return 2694 | if (this._unbind) this._unbind() 2695 | this.vm = this.el = this.binding = this.compiler = null 2696 | } 2697 | 2698 | // Exposed static methods ----------------------------------------------------- 2699 | 2700 | /** 2701 | * Parse a directive string into an Array of 2702 | * AST-like objects representing directives 2703 | */ 2704 | Directive.parse = function (str) { 2705 | 2706 | var inSingle = false, 2707 | inDouble = false, 2708 | curly = 0, 2709 | square = 0, 2710 | paren = 0, 2711 | begin = 0, 2712 | argIndex = 0, 2713 | dirs = [], 2714 | dir = {}, 2715 | lastFilterIndex = 0, 2716 | arg 2717 | 2718 | for (var c, i = 0, l = str.length; i < l; i++) { 2719 | c = str.charAt(i) 2720 | if (inSingle) { 2721 | // check single quote 2722 | if (c === "'") inSingle = !inSingle 2723 | } else if (inDouble) { 2724 | // check double quote 2725 | if (c === '"') inDouble = !inDouble 2726 | } else if (c === ',' && !paren && !curly && !square) { 2727 | // reached the end of a directive 2728 | pushDir() 2729 | // reset & skip the comma 2730 | dir = {} 2731 | begin = argIndex = lastFilterIndex = i + 1 2732 | } else if (c === ':' && !dir.key && !dir.arg) { 2733 | // argument 2734 | arg = str.slice(begin, i).trim() 2735 | if (ARG_RE.test(arg)) { 2736 | argIndex = i + 1 2737 | dir.arg = str.slice(begin, i).trim() 2738 | } 2739 | } else if (c === '|' && str.charAt(i + 1) !== '|' && str.charAt(i - 1) !== '|') { 2740 | if (dir.key === undefined) { 2741 | // first filter, end of key 2742 | lastFilterIndex = i + 1 2743 | dir.key = str.slice(argIndex, i).trim() 2744 | } else { 2745 | // already has filter 2746 | pushFilter() 2747 | } 2748 | } else if (c === '"') { 2749 | inDouble = true 2750 | } else if (c === "'") { 2751 | inSingle = true 2752 | } else if (c === '(') { 2753 | paren++ 2754 | } else if (c === ')') { 2755 | paren-- 2756 | } else if (c === '[') { 2757 | square++ 2758 | } else if (c === ']') { 2759 | square-- 2760 | } else if (c === '{') { 2761 | curly++ 2762 | } else if (c === '}') { 2763 | curly-- 2764 | } 2765 | } 2766 | if (i === 0 || begin !== i) { 2767 | pushDir() 2768 | } 2769 | 2770 | function pushDir () { 2771 | dir.expression = str.slice(begin, i).trim() 2772 | if (dir.key === undefined) { 2773 | dir.key = str.slice(argIndex, i).trim() 2774 | } else if (lastFilterIndex !== begin) { 2775 | pushFilter() 2776 | } 2777 | if (i === 0 || dir.key) { 2778 | dirs.push(dir) 2779 | } 2780 | } 2781 | 2782 | function pushFilter () { 2783 | var exp = str.slice(lastFilterIndex, i).trim(), 2784 | filter 2785 | if (exp) { 2786 | filter = {} 2787 | var tokens = exp.match(FILTER_TOKEN_RE) 2788 | filter.name = tokens[0] 2789 | filter.args = tokens.length > 1 ? tokens.slice(1) : null 2790 | } 2791 | if (filter) { 2792 | (dir.filters = dir.filters || []).push(filter) 2793 | } 2794 | lastFilterIndex = i + 1 2795 | } 2796 | 2797 | return dirs 2798 | } 2799 | 2800 | /** 2801 | * Inline computed filters so they become part 2802 | * of the expression 2803 | */ 2804 | Directive.inlineFilters = function (key, filters) { 2805 | var args, filter 2806 | for (var i = 0, l = filters.length; i < l; i++) { 2807 | filter = filters[i] 2808 | args = filter.args 2809 | ? ',"' + filter.args.map(escapeQuote).join('","') + '"' 2810 | : '' 2811 | key = 'this.$compiler.getOption("filters", "' + 2812 | filter.name + 2813 | '").call(this,' + 2814 | key + args + 2815 | ')' 2816 | } 2817 | return key 2818 | } 2819 | 2820 | /** 2821 | * Convert double quotes to single quotes 2822 | * so they don't mess up the generated function body 2823 | */ 2824 | function escapeQuote (v) { 2825 | return v.indexOf('"') > -1 2826 | ? v.replace(QUOTE_RE, '\'') 2827 | : v 2828 | } 2829 | 2830 | module.exports = Directive 2831 | }); 2832 | require.register("vue/src/exp-parser.js", function(exports, require, module){ 2833 | var utils = require('./utils'), 2834 | STR_SAVE_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, 2835 | STR_RESTORE_RE = /"(\d+)"/g, 2836 | NEWLINE_RE = /\n/g, 2837 | CTOR_RE = new RegExp('constructor'.split('').join('[\'"+, ]*')), 2838 | UNICODE_RE = /\\u\d\d\d\d/ 2839 | 2840 | // Variable extraction scooped from https://github.com/RubyLouvre/avalon 2841 | 2842 | var KEYWORDS = 2843 | // keywords 2844 | 'break,case,catch,continue,debugger,default,delete,do,else,false' + 2845 | ',finally,for,function,if,in,instanceof,new,null,return,switch,this' + 2846 | ',throw,true,try,typeof,var,void,while,with,undefined' + 2847 | // reserved 2848 | ',abstract,boolean,byte,char,class,const,double,enum,export,extends' + 2849 | ',final,float,goto,implements,import,int,interface,long,native' + 2850 | ',package,private,protected,public,short,static,super,synchronized' + 2851 | ',throws,transient,volatile' + 2852 | // ECMA 5 - use strict 2853 | ',arguments,let,yield' + 2854 | // allow using Math in expressions 2855 | ',Math', 2856 | 2857 | KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g'), 2858 | REMOVE_RE = /\/\*(?:.|\n)*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|'[^']*'|"[^"]*"|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g, 2859 | SPLIT_RE = /[^\w$]+/g, 2860 | NUMBER_RE = /\b\d[^,]*/g, 2861 | BOUNDARY_RE = /^,+|,+$/g 2862 | 2863 | /** 2864 | * Strip top level variable names from a snippet of JS expression 2865 | */ 2866 | function getVariables (code) { 2867 | code = code 2868 | .replace(REMOVE_RE, '') 2869 | .replace(SPLIT_RE, ',') 2870 | .replace(KEYWORDS_RE, '') 2871 | .replace(NUMBER_RE, '') 2872 | .replace(BOUNDARY_RE, '') 2873 | return code 2874 | ? code.split(/,+/) 2875 | : [] 2876 | } 2877 | 2878 | /** 2879 | * A given path could potentially exist not on the 2880 | * current compiler, but up in the parent chain somewhere. 2881 | * This function generates an access relationship string 2882 | * that can be used in the getter function by walking up 2883 | * the parent chain to check for key existence. 2884 | * 2885 | * It stops at top parent if no vm in the chain has the 2886 | * key. It then creates any missing bindings on the 2887 | * final resolved vm. 2888 | */ 2889 | function traceScope (path, compiler, data) { 2890 | var rel = '', 2891 | dist = 0, 2892 | self = compiler 2893 | 2894 | if (data && utils.get(data, path) !== undefined) { 2895 | // hack: temporarily attached data 2896 | return '$temp.' 2897 | } 2898 | 2899 | while (compiler) { 2900 | if (compiler.hasKey(path)) { 2901 | break 2902 | } else { 2903 | compiler = compiler.parent 2904 | dist++ 2905 | } 2906 | } 2907 | if (compiler) { 2908 | while (dist--) { 2909 | rel += '$parent.' 2910 | } 2911 | if (!compiler.bindings[path] && path.charAt(0) !== '$') { 2912 | compiler.createBinding(path) 2913 | } 2914 | } else { 2915 | self.createBinding(path) 2916 | } 2917 | return rel 2918 | } 2919 | 2920 | /** 2921 | * Create a function from a string... 2922 | * this looks like evil magic but since all variables are limited 2923 | * to the VM's data it's actually properly sandboxed 2924 | */ 2925 | function makeGetter (exp, raw) { 2926 | var fn 2927 | try { 2928 | fn = new Function(exp) 2929 | } catch (e) { 2930 | utils.warn('Error parsing expression: ' + raw) 2931 | } 2932 | return fn 2933 | } 2934 | 2935 | /** 2936 | * Escape a leading dollar sign for regex construction 2937 | */ 2938 | function escapeDollar (v) { 2939 | return v.charAt(0) === '$' 2940 | ? '\\' + v 2941 | : v 2942 | } 2943 | 2944 | /** 2945 | * Parse and return an anonymous computed property getter function 2946 | * from an arbitrary expression, together with a list of paths to be 2947 | * created as bindings. 2948 | */ 2949 | exports.parse = function (exp, compiler, data) { 2950 | // unicode and 'constructor' are not allowed for XSS security. 2951 | if (UNICODE_RE.test(exp) || CTOR_RE.test(exp)) { 2952 | utils.warn('Unsafe expression: ' + exp) 2953 | return 2954 | } 2955 | // extract variable names 2956 | var vars = getVariables(exp) 2957 | if (!vars.length) { 2958 | return makeGetter('return ' + exp, exp) 2959 | } 2960 | vars = utils.unique(vars) 2961 | 2962 | var accessors = '', 2963 | has = utils.hash(), 2964 | strings = [], 2965 | // construct a regex to extract all valid variable paths 2966 | // ones that begin with "$" are particularly tricky 2967 | // because we can't use \b for them 2968 | pathRE = new RegExp( 2969 | "[^$\\w\\.](" + 2970 | vars.map(escapeDollar).join('|') + 2971 | ")[$\\w\\.]*\\b", 'g' 2972 | ), 2973 | body = (' ' + exp) 2974 | .replace(STR_SAVE_RE, saveStrings) 2975 | .replace(pathRE, replacePath) 2976 | .replace(STR_RESTORE_RE, restoreStrings) 2977 | 2978 | body = accessors + 'return ' + body 2979 | 2980 | function saveStrings (str) { 2981 | var i = strings.length 2982 | // escape newlines in strings so the expression 2983 | // can be correctly evaluated 2984 | strings[i] = str.replace(NEWLINE_RE, '\\n') 2985 | return '"' + i + '"' 2986 | } 2987 | 2988 | function replacePath (path) { 2989 | // keep track of the first char 2990 | var c = path.charAt(0) 2991 | path = path.slice(1) 2992 | var val = 'this.' + traceScope(path, compiler, data) + path 2993 | if (!has[path]) { 2994 | accessors += val + ';' 2995 | has[path] = 1 2996 | } 2997 | // don't forget to put that first char back 2998 | return c + val 2999 | } 3000 | 3001 | function restoreStrings (str, i) { 3002 | return strings[i] 3003 | } 3004 | 3005 | return makeGetter(body, exp) 3006 | } 3007 | 3008 | /** 3009 | * Evaluate an expression in the context of a compiler. 3010 | * Accepts additional data. 3011 | */ 3012 | exports.eval = function (exp, compiler, data) { 3013 | var getter = exports.parse(exp, compiler, data), res 3014 | if (getter) { 3015 | // hack: temporarily attach the additional data so 3016 | // it can be accessed in the getter 3017 | compiler.vm.$temp = data 3018 | res = getter.call(compiler.vm) 3019 | delete compiler.vm.$temp 3020 | } 3021 | return res 3022 | } 3023 | }); 3024 | require.register("vue/src/text-parser.js", function(exports, require, module){ 3025 | var openChar = '{', 3026 | endChar = '}', 3027 | ESCAPE_RE = /[-.*+?^${}()|[\]\/\\]/g, 3028 | BINDING_RE = buildInterpolationRegex(), 3029 | // lazy require 3030 | Directive 3031 | 3032 | function buildInterpolationRegex () { 3033 | var open = escapeRegex(openChar), 3034 | end = escapeRegex(endChar) 3035 | return new RegExp(open + open + open + '?(.+?)' + end + '?' + end + end) 3036 | } 3037 | 3038 | function escapeRegex (str) { 3039 | return str.replace(ESCAPE_RE, '\\$&') 3040 | } 3041 | 3042 | function setDelimiters (delimiters) { 3043 | exports.delimiters = delimiters 3044 | openChar = delimiters[0] 3045 | endChar = delimiters[1] 3046 | BINDING_RE = buildInterpolationRegex() 3047 | } 3048 | 3049 | /** 3050 | * Parse a piece of text, return an array of tokens 3051 | * token types: 3052 | * 1. plain string 3053 | * 2. object with key = binding key 3054 | * 3. object with key & html = true 3055 | */ 3056 | function parse (text) { 3057 | if (!BINDING_RE.test(text)) return null 3058 | var m, i, token, match, tokens = [] 3059 | /* jshint boss: true */ 3060 | while (m = text.match(BINDING_RE)) { 3061 | i = m.index 3062 | if (i > 0) tokens.push(text.slice(0, i)) 3063 | token = { key: m[1].trim() } 3064 | match = m[0] 3065 | token.html = 3066 | match.charAt(2) === openChar && 3067 | match.charAt(match.length - 3) === endChar 3068 | tokens.push(token) 3069 | text = text.slice(i + m[0].length) 3070 | } 3071 | if (text.length) tokens.push(text) 3072 | return tokens 3073 | } 3074 | 3075 | /** 3076 | * Parse an attribute value with possible interpolation tags 3077 | * return a Directive-friendly expression 3078 | * 3079 | * e.g. a {{b}} c => "a " + b + " c" 3080 | */ 3081 | function parseAttr (attr) { 3082 | Directive = Directive || require('./directive') 3083 | var tokens = parse(attr) 3084 | if (!tokens) return null 3085 | if (tokens.length === 1) return tokens[0].key 3086 | var res = [], token 3087 | for (var i = 0, l = tokens.length; i < l; i++) { 3088 | token = tokens[i] 3089 | res.push( 3090 | token.key 3091 | ? inlineFilters(token.key) 3092 | : ('"' + token + '"') 3093 | ) 3094 | } 3095 | return res.join('+') 3096 | } 3097 | 3098 | /** 3099 | * Inlines any possible filters in a binding 3100 | * so that we can combine everything into a huge expression 3101 | */ 3102 | function inlineFilters (key) { 3103 | if (key.indexOf('|') > -1) { 3104 | var dirs = Directive.parse(key), 3105 | dir = dirs && dirs[0] 3106 | if (dir && dir.filters) { 3107 | key = Directive.inlineFilters( 3108 | dir.key, 3109 | dir.filters 3110 | ) 3111 | } 3112 | } 3113 | return '(' + key + ')' 3114 | } 3115 | 3116 | exports.parse = parse 3117 | exports.parseAttr = parseAttr 3118 | exports.setDelimiters = setDelimiters 3119 | exports.delimiters = [openChar, endChar] 3120 | }); 3121 | require.register("vue/src/deps-parser.js", function(exports, require, module){ 3122 | var Emitter = require('./emitter'), 3123 | utils = require('./utils'), 3124 | Observer = require('./observer'), 3125 | catcher = new Emitter() 3126 | 3127 | /** 3128 | * Auto-extract the dependencies of a computed property 3129 | * by recording the getters triggered when evaluating it. 3130 | */ 3131 | function catchDeps (binding) { 3132 | if (binding.isFn) return 3133 | utils.log('\n- ' + binding.key) 3134 | var got = utils.hash() 3135 | binding.deps = [] 3136 | catcher.on('get', function (dep) { 3137 | var has = got[dep.key] 3138 | if ( 3139 | // avoid duplicate bindings 3140 | (has && has.compiler === dep.compiler) || 3141 | // avoid repeated items as dependency 3142 | // only when the binding is from self or the parent chain 3143 | (dep.compiler.repeat && !isParentOf(dep.compiler, binding.compiler)) 3144 | ) { 3145 | return 3146 | } 3147 | got[dep.key] = dep 3148 | utils.log(' - ' + dep.key) 3149 | binding.deps.push(dep) 3150 | dep.subs.push(binding) 3151 | }) 3152 | binding.value.$get() 3153 | catcher.off('get') 3154 | } 3155 | 3156 | /** 3157 | * Test if A is a parent of or equals B 3158 | */ 3159 | function isParentOf (a, b) { 3160 | while (b) { 3161 | if (a === b) { 3162 | return true 3163 | } 3164 | b = b.parent 3165 | } 3166 | } 3167 | 3168 | module.exports = { 3169 | 3170 | /** 3171 | * the observer that catches events triggered by getters 3172 | */ 3173 | catcher: catcher, 3174 | 3175 | /** 3176 | * parse a list of computed property bindings 3177 | */ 3178 | parse: function (bindings) { 3179 | utils.log('\nparsing dependencies...') 3180 | Observer.shouldGet = true 3181 | bindings.forEach(catchDeps) 3182 | Observer.shouldGet = false 3183 | utils.log('\ndone.') 3184 | } 3185 | 3186 | } 3187 | }); 3188 | require.register("vue/src/filters.js", function(exports, require, module){ 3189 | var utils = require('./utils'), 3190 | get = utils.get, 3191 | slice = [].slice, 3192 | QUOTE_RE = /^'.*'$/, 3193 | filters = module.exports = utils.hash() 3194 | 3195 | /** 3196 | * 'abc' => 'Abc' 3197 | */ 3198 | filters.capitalize = function (value) { 3199 | if (!value && value !== 0) return '' 3200 | value = value.toString() 3201 | return value.charAt(0).toUpperCase() + value.slice(1) 3202 | } 3203 | 3204 | /** 3205 | * 'abc' => 'ABC' 3206 | */ 3207 | filters.uppercase = function (value) { 3208 | return (value || value === 0) 3209 | ? value.toString().toUpperCase() 3210 | : '' 3211 | } 3212 | 3213 | /** 3214 | * 'AbC' => 'abc' 3215 | */ 3216 | filters.lowercase = function (value) { 3217 | return (value || value === 0) 3218 | ? value.toString().toLowerCase() 3219 | : '' 3220 | } 3221 | 3222 | /** 3223 | * 12345 => $12,345.00 3224 | */ 3225 | filters.currency = function (value, sign) { 3226 | if (!value && value !== 0) return '' 3227 | sign = sign || '$' 3228 | var s = Math.floor(value).toString(), 3229 | i = s.length % 3, 3230 | h = i > 0 ? (s.slice(0, i) + (s.length > 3 ? ',' : '')) : '', 3231 | f = '.' + value.toFixed(2).slice(-2) 3232 | return sign + h + s.slice(i).replace(/(\d{3})(?=\d)/g, '$1,') + f 3233 | } 3234 | 3235 | /** 3236 | * args: an array of strings corresponding to 3237 | * the single, double, triple ... forms of the word to 3238 | * be pluralized. When the number to be pluralized 3239 | * exceeds the length of the args, it will use the last 3240 | * entry in the array. 3241 | * 3242 | * e.g. ['single', 'double', 'triple', 'multiple'] 3243 | */ 3244 | filters.pluralize = function (value) { 3245 | var args = slice.call(arguments, 1) 3246 | return args.length > 1 3247 | ? (args[value - 1] || args[args.length - 1]) 3248 | : (args[value - 1] || args[0] + 's') 3249 | } 3250 | 3251 | /** 3252 | * A special filter that takes a handler function, 3253 | * wraps it so it only gets triggered on specific keypresses. 3254 | * 3255 | * v-on only 3256 | */ 3257 | 3258 | var keyCodes = { 3259 | enter : 13, 3260 | tab : 9, 3261 | 'delete' : 46, 3262 | up : 38, 3263 | left : 37, 3264 | right : 39, 3265 | down : 40, 3266 | esc : 27 3267 | } 3268 | 3269 | filters.key = function (handler, key) { 3270 | if (!handler) return 3271 | var code = keyCodes[key] 3272 | if (!code) { 3273 | code = parseInt(key, 10) 3274 | } 3275 | return function (e) { 3276 | if (e.keyCode === code) { 3277 | return handler.call(this, e) 3278 | } 3279 | } 3280 | } 3281 | 3282 | /** 3283 | * Filter filter for v-repeat 3284 | */ 3285 | filters.filterBy = function (arr, searchKey, delimiter, dataKey) { 3286 | 3287 | // allow optional `in` delimiter 3288 | // because why not 3289 | if (delimiter && delimiter !== 'in') { 3290 | dataKey = delimiter 3291 | } 3292 | 3293 | // get the search string 3294 | var search = stripQuotes(searchKey) || this.$get(searchKey) 3295 | if (!search) return arr 3296 | search = search.toLowerCase() 3297 | 3298 | // get the optional dataKey 3299 | dataKey = dataKey && (stripQuotes(dataKey) || this.$get(dataKey)) 3300 | 3301 | // convert object to array 3302 | if (!Array.isArray(arr)) { 3303 | arr = utils.objectToArray(arr) 3304 | } 3305 | 3306 | return arr.filter(function (item) { 3307 | return dataKey 3308 | ? contains(get(item, dataKey), search) 3309 | : contains(item, search) 3310 | }) 3311 | 3312 | } 3313 | 3314 | filters.filterBy.computed = true 3315 | 3316 | /** 3317 | * Sort fitler for v-repeat 3318 | */ 3319 | filters.orderBy = function (arr, sortKey, reverseKey) { 3320 | 3321 | var key = stripQuotes(sortKey) || this.$get(sortKey) 3322 | if (!key) return arr 3323 | 3324 | // convert object to array 3325 | if (!Array.isArray(arr)) { 3326 | arr = utils.objectToArray(arr) 3327 | } 3328 | 3329 | var order = 1 3330 | if (reverseKey) { 3331 | if (reverseKey === '-1') { 3332 | order = -1 3333 | } else if (reverseKey.charAt(0) === '!') { 3334 | reverseKey = reverseKey.slice(1) 3335 | order = this.$get(reverseKey) ? 1 : -1 3336 | } else { 3337 | order = this.$get(reverseKey) ? -1 : 1 3338 | } 3339 | } 3340 | 3341 | // sort on a copy to avoid mutating original array 3342 | return arr.slice().sort(function (a, b) { 3343 | a = get(a, key) 3344 | b = get(b, key) 3345 | return a === b ? 0 : a > b ? order : -order 3346 | }) 3347 | 3348 | } 3349 | 3350 | filters.orderBy.computed = true 3351 | 3352 | // Array filter helpers ------------------------------------------------------- 3353 | 3354 | /** 3355 | * String contain helper 3356 | */ 3357 | function contains (val, search) { 3358 | /* jshint eqeqeq: false */ 3359 | if (utils.isObject(val)) { 3360 | for (var key in val) { 3361 | if (contains(val[key], search)) { 3362 | return true 3363 | } 3364 | } 3365 | } else if (val != null) { 3366 | return val.toString().toLowerCase().indexOf(search) > -1 3367 | } 3368 | } 3369 | 3370 | /** 3371 | * Test whether a string is in quotes, 3372 | * if yes return stripped string 3373 | */ 3374 | function stripQuotes (str) { 3375 | if (QUOTE_RE.test(str)) { 3376 | return str.slice(1, -1) 3377 | } 3378 | } 3379 | }); 3380 | require.register("vue/src/transition.js", function(exports, require, module){ 3381 | var endEvents = sniffEndEvents(), 3382 | config = require('./config'), 3383 | // batch enter animations so we only force the layout once 3384 | Batcher = require('./batcher'), 3385 | batcher = new Batcher(), 3386 | // cache timer functions 3387 | setTO = window.setTimeout, 3388 | clearTO = window.clearTimeout, 3389 | // exit codes for testing 3390 | codes = { 3391 | CSS_E : 1, 3392 | CSS_L : 2, 3393 | JS_E : 3, 3394 | JS_L : 4, 3395 | CSS_SKIP : -1, 3396 | JS_SKIP : -2, 3397 | JS_SKIP_E : -3, 3398 | JS_SKIP_L : -4, 3399 | INIT : -5, 3400 | SKIP : -6 3401 | } 3402 | 3403 | // force layout before triggering transitions/animations 3404 | batcher._preFlush = function () { 3405 | /* jshint unused: false */ 3406 | var f = document.body.offsetHeight 3407 | } 3408 | 3409 | /** 3410 | * stage: 3411 | * 1 = enter 3412 | * 2 = leave 3413 | */ 3414 | var transition = module.exports = function (el, stage, cb, compiler) { 3415 | 3416 | var changeState = function () { 3417 | cb() 3418 | compiler.execHook(stage > 0 ? 'attached' : 'detached') 3419 | } 3420 | 3421 | if (compiler.init) { 3422 | changeState() 3423 | return codes.INIT 3424 | } 3425 | 3426 | var hasTransition = el.vue_trans === '', 3427 | hasAnimation = el.vue_anim === '', 3428 | effectId = el.vue_effect 3429 | 3430 | if (effectId) { 3431 | return applyTransitionFunctions( 3432 | el, 3433 | stage, 3434 | changeState, 3435 | effectId, 3436 | compiler 3437 | ) 3438 | } else if (hasTransition || hasAnimation) { 3439 | return applyTransitionClass( 3440 | el, 3441 | stage, 3442 | changeState, 3443 | hasAnimation 3444 | ) 3445 | } else { 3446 | changeState() 3447 | return codes.SKIP 3448 | } 3449 | 3450 | } 3451 | 3452 | transition.codes = codes 3453 | 3454 | /** 3455 | * Togggle a CSS class to trigger transition 3456 | */ 3457 | function applyTransitionClass (el, stage, changeState, hasAnimation) { 3458 | 3459 | if (!endEvents.trans) { 3460 | changeState() 3461 | return codes.CSS_SKIP 3462 | } 3463 | 3464 | // if the browser supports transition, 3465 | // it must have classList... 3466 | var onEnd, 3467 | classList = el.classList, 3468 | existingCallback = el.vue_trans_cb, 3469 | enterClass = config.enterClass, 3470 | leaveClass = config.leaveClass, 3471 | endEvent = hasAnimation ? endEvents.anim : endEvents.trans 3472 | 3473 | // cancel unfinished callbacks and jobs 3474 | if (existingCallback) { 3475 | el.removeEventListener(endEvent, existingCallback) 3476 | classList.remove(enterClass) 3477 | classList.remove(leaveClass) 3478 | el.vue_trans_cb = null 3479 | } 3480 | 3481 | if (stage > 0) { // enter 3482 | 3483 | // set to enter state before appending 3484 | classList.add(enterClass) 3485 | // append 3486 | changeState() 3487 | // trigger transition 3488 | if (!hasAnimation) { 3489 | batcher.push({ 3490 | execute: function () { 3491 | classList.remove(enterClass) 3492 | } 3493 | }) 3494 | } else { 3495 | onEnd = function (e) { 3496 | if (e.target === el) { 3497 | el.removeEventListener(endEvent, onEnd) 3498 | el.vue_trans_cb = null 3499 | classList.remove(enterClass) 3500 | } 3501 | } 3502 | el.addEventListener(endEvent, onEnd) 3503 | el.vue_trans_cb = onEnd 3504 | } 3505 | return codes.CSS_E 3506 | 3507 | } else { // leave 3508 | 3509 | if (el.offsetWidth || el.offsetHeight) { 3510 | // trigger hide transition 3511 | classList.add(leaveClass) 3512 | onEnd = function (e) { 3513 | if (e.target === el) { 3514 | el.removeEventListener(endEvent, onEnd) 3515 | el.vue_trans_cb = null 3516 | // actually remove node here 3517 | changeState() 3518 | classList.remove(leaveClass) 3519 | } 3520 | } 3521 | // attach transition end listener 3522 | el.addEventListener(endEvent, onEnd) 3523 | el.vue_trans_cb = onEnd 3524 | } else { 3525 | // directly remove invisible elements 3526 | changeState() 3527 | } 3528 | return codes.CSS_L 3529 | 3530 | } 3531 | 3532 | } 3533 | 3534 | function applyTransitionFunctions (el, stage, changeState, effectId, compiler) { 3535 | 3536 | var funcs = compiler.getOption('effects', effectId) 3537 | if (!funcs) { 3538 | changeState() 3539 | return codes.JS_SKIP 3540 | } 3541 | 3542 | var enter = funcs.enter, 3543 | leave = funcs.leave, 3544 | timeouts = el.vue_timeouts 3545 | 3546 | // clear previous timeouts 3547 | if (timeouts) { 3548 | var i = timeouts.length 3549 | while (i--) { 3550 | clearTO(timeouts[i]) 3551 | } 3552 | } 3553 | 3554 | timeouts = el.vue_timeouts = [] 3555 | function timeout (cb, delay) { 3556 | var id = setTO(function () { 3557 | cb() 3558 | timeouts.splice(timeouts.indexOf(id), 1) 3559 | if (!timeouts.length) { 3560 | el.vue_timeouts = null 3561 | } 3562 | }, delay) 3563 | timeouts.push(id) 3564 | } 3565 | 3566 | if (stage > 0) { // enter 3567 | if (typeof enter !== 'function') { 3568 | changeState() 3569 | return codes.JS_SKIP_E 3570 | } 3571 | enter(el, changeState, timeout) 3572 | return codes.JS_E 3573 | } else { // leave 3574 | if (typeof leave !== 'function') { 3575 | changeState() 3576 | return codes.JS_SKIP_L 3577 | } 3578 | leave(el, changeState, timeout) 3579 | return codes.JS_L 3580 | } 3581 | 3582 | } 3583 | 3584 | /** 3585 | * Sniff proper transition end event name 3586 | */ 3587 | function sniffEndEvents () { 3588 | var el = document.createElement('vue'), 3589 | defaultEvent = 'transitionend', 3590 | events = { 3591 | 'transition' : defaultEvent, 3592 | 'mozTransition' : defaultEvent, 3593 | 'webkitTransition' : 'webkitTransitionEnd' 3594 | }, 3595 | ret = {} 3596 | for (var name in events) { 3597 | if (el.style[name] !== undefined) { 3598 | ret.trans = events[name] 3599 | break 3600 | } 3601 | } 3602 | ret.anim = el.style.animation === '' 3603 | ? 'animationend' 3604 | : 'webkitAnimationEnd' 3605 | return ret 3606 | } 3607 | }); 3608 | require.register("vue/src/batcher.js", function(exports, require, module){ 3609 | var utils = require('./utils') 3610 | 3611 | function Batcher () { 3612 | this.reset() 3613 | } 3614 | 3615 | var BatcherProto = Batcher.prototype 3616 | 3617 | BatcherProto.push = function (job) { 3618 | if (!job.id || !this.has[job.id]) { 3619 | this.queue.push(job) 3620 | this.has[job.id] = job 3621 | if (!this.waiting) { 3622 | this.waiting = true 3623 | utils.nextTick(utils.bind(this.flush, this)) 3624 | } 3625 | } else if (job.override) { 3626 | var oldJob = this.has[job.id] 3627 | oldJob.cancelled = true 3628 | this.queue.push(job) 3629 | this.has[job.id] = job 3630 | } 3631 | } 3632 | 3633 | BatcherProto.flush = function () { 3634 | // before flush hook 3635 | if (this._preFlush) this._preFlush() 3636 | // do not cache length because more jobs might be pushed 3637 | // as we execute existing jobs 3638 | for (var i = 0; i < this.queue.length; i++) { 3639 | var job = this.queue[i] 3640 | if (!job.cancelled) { 3641 | job.execute() 3642 | } 3643 | } 3644 | this.reset() 3645 | } 3646 | 3647 | BatcherProto.reset = function () { 3648 | this.has = utils.hash() 3649 | this.queue = [] 3650 | this.waiting = false 3651 | } 3652 | 3653 | module.exports = Batcher 3654 | }); 3655 | require.register("vue/src/directives/index.js", function(exports, require, module){ 3656 | var utils = require('../utils'), 3657 | config = require('../config'), 3658 | transition = require('../transition'), 3659 | directives = module.exports = utils.hash() 3660 | 3661 | /** 3662 | * Nest and manage a Child VM 3663 | */ 3664 | directives.component = { 3665 | isLiteral: true, 3666 | bind: function () { 3667 | if (!this.el.vue_vm) { 3668 | this.childVM = new this.Ctor({ 3669 | el: this.el, 3670 | parent: this.vm 3671 | }) 3672 | } 3673 | }, 3674 | unbind: function () { 3675 | if (this.childVM) { 3676 | this.childVM.$destroy() 3677 | } 3678 | } 3679 | } 3680 | 3681 | /** 3682 | * Binding HTML attributes 3683 | */ 3684 | directives.attr = { 3685 | bind: function () { 3686 | var params = this.vm.$options.paramAttributes 3687 | this.isParam = params && params.indexOf(this.arg) > -1 3688 | }, 3689 | update: function (value) { 3690 | if (value || value === 0) { 3691 | this.el.setAttribute(this.arg, value) 3692 | } else { 3693 | this.el.removeAttribute(this.arg) 3694 | } 3695 | if (this.isParam) { 3696 | this.vm[this.arg] = utils.checkNumber(value) 3697 | } 3698 | } 3699 | } 3700 | 3701 | /** 3702 | * Binding textContent 3703 | */ 3704 | directives.text = { 3705 | bind: function () { 3706 | this.attr = this.el.nodeType === 3 3707 | ? 'nodeValue' 3708 | : 'textContent' 3709 | }, 3710 | update: function (value) { 3711 | this.el[this.attr] = utils.guard(value) 3712 | } 3713 | } 3714 | 3715 | /** 3716 | * Binding CSS display property 3717 | */ 3718 | directives.show = function (value) { 3719 | var el = this.el, 3720 | target = value ? '' : 'none', 3721 | change = function () { 3722 | el.style.display = target 3723 | } 3724 | transition(el, value ? 1 : -1, change, this.compiler) 3725 | } 3726 | 3727 | /** 3728 | * Binding CSS classes 3729 | */ 3730 | directives['class'] = function (value) { 3731 | if (this.arg) { 3732 | utils[value ? 'addClass' : 'removeClass'](this.el, this.arg) 3733 | } else { 3734 | if (this.lastVal) { 3735 | utils.removeClass(this.el, this.lastVal) 3736 | } 3737 | if (value) { 3738 | utils.addClass(this.el, value) 3739 | this.lastVal = value 3740 | } 3741 | } 3742 | } 3743 | 3744 | /** 3745 | * Only removed after the owner VM is ready 3746 | */ 3747 | directives.cloak = { 3748 | isEmpty: true, 3749 | bind: function () { 3750 | var el = this.el 3751 | this.compiler.observer.once('hook:ready', function () { 3752 | el.removeAttribute(config.prefix + '-cloak') 3753 | }) 3754 | } 3755 | } 3756 | 3757 | /** 3758 | * Store a reference to self in parent VM's $ 3759 | */ 3760 | directives.ref = { 3761 | isLiteral: true, 3762 | bind: function () { 3763 | var id = this.expression 3764 | if (id) { 3765 | this.vm.$parent.$[id] = this.vm 3766 | } 3767 | }, 3768 | unbind: function () { 3769 | var id = this.expression 3770 | if (id) { 3771 | delete this.vm.$parent.$[id] 3772 | } 3773 | } 3774 | } 3775 | 3776 | directives.on = require('./on') 3777 | directives.repeat = require('./repeat') 3778 | directives.model = require('./model') 3779 | directives['if'] = require('./if') 3780 | directives['with'] = require('./with') 3781 | directives.html = require('./html') 3782 | directives.style = require('./style') 3783 | directives.partial = require('./partial') 3784 | directives.view = require('./view') 3785 | }); 3786 | require.register("vue/src/directives/if.js", function(exports, require, module){ 3787 | var utils = require('../utils') 3788 | 3789 | /** 3790 | * Manages a conditional child VM 3791 | */ 3792 | module.exports = { 3793 | 3794 | bind: function () { 3795 | 3796 | this.parent = this.el.parentNode 3797 | this.ref = document.createComment('vue-if') 3798 | this.Ctor = this.compiler.resolveComponent(this.el) 3799 | 3800 | // insert ref 3801 | this.parent.insertBefore(this.ref, this.el) 3802 | this.parent.removeChild(this.el) 3803 | 3804 | if (utils.attr(this.el, 'view')) { 3805 | utils.warn( 3806 | 'Conflict: v-if cannot be used together with v-view. ' + 3807 | 'Just set v-view\'s binding value to empty string to empty it.' 3808 | ) 3809 | } 3810 | if (utils.attr(this.el, 'repeat')) { 3811 | utils.warn( 3812 | 'Conflict: v-if cannot be used together with v-repeat. ' + 3813 | 'Use `v-show` or the `filterBy` filter instead.' 3814 | ) 3815 | } 3816 | }, 3817 | 3818 | update: function (value) { 3819 | 3820 | if (!value) { 3821 | this._unbind() 3822 | } else if (!this.childVM) { 3823 | this.childVM = new this.Ctor({ 3824 | el: this.el.cloneNode(true), 3825 | parent: this.vm 3826 | }) 3827 | if (this.compiler.init) { 3828 | this.parent.insertBefore(this.childVM.$el, this.ref) 3829 | } else { 3830 | this.childVM.$before(this.ref) 3831 | } 3832 | } 3833 | 3834 | }, 3835 | 3836 | unbind: function () { 3837 | if (this.childVM) { 3838 | this.childVM.$destroy() 3839 | this.childVM = null 3840 | } 3841 | } 3842 | } 3843 | }); 3844 | require.register("vue/src/directives/repeat.js", function(exports, require, module){ 3845 | var utils = require('../utils'), 3846 | config = require('../config') 3847 | 3848 | /** 3849 | * Binding that manages VMs based on an Array 3850 | */ 3851 | module.exports = { 3852 | 3853 | bind: function () { 3854 | 3855 | this.identifier = '$r' + this.id 3856 | 3857 | // a hash to cache the same expressions on repeated instances 3858 | // so they don't have to be compiled for every single instance 3859 | this.expCache = utils.hash() 3860 | 3861 | var el = this.el, 3862 | ctn = this.container = el.parentNode 3863 | 3864 | // extract child Id, if any 3865 | this.childId = this.compiler.eval(utils.attr(el, 'ref')) 3866 | 3867 | // create a comment node as a reference node for DOM insertions 3868 | this.ref = document.createComment(config.prefix + '-repeat-' + this.key) 3869 | ctn.insertBefore(this.ref, el) 3870 | ctn.removeChild(el) 3871 | 3872 | this.collection = null 3873 | this.vms = null 3874 | 3875 | }, 3876 | 3877 | update: function (collection) { 3878 | 3879 | if (!Array.isArray(collection)) { 3880 | if (utils.isObject(collection)) { 3881 | collection = utils.objectToArray(collection) 3882 | } else { 3883 | utils.warn('v-repeat only accepts Array or Object values.') 3884 | } 3885 | } 3886 | 3887 | // keep reference of old data and VMs 3888 | // so we can reuse them if possible 3889 | this.oldVMs = this.vms 3890 | this.oldCollection = this.collection 3891 | collection = this.collection = collection || [] 3892 | 3893 | var isObject = collection[0] && utils.isObject(collection[0]) 3894 | this.vms = this.oldCollection 3895 | ? this.diff(collection, isObject) 3896 | : this.init(collection, isObject) 3897 | 3898 | if (this.childId) { 3899 | this.vm.$[this.childId] = this.vms 3900 | } 3901 | 3902 | }, 3903 | 3904 | init: function (collection, isObject) { 3905 | var vm, vms = [] 3906 | for (var i = 0, l = collection.length; i < l; i++) { 3907 | vm = this.build(collection[i], i, isObject) 3908 | vms.push(vm) 3909 | if (this.compiler.init) { 3910 | this.container.insertBefore(vm.$el, this.ref) 3911 | } else { 3912 | vm.$before(this.ref) 3913 | } 3914 | } 3915 | return vms 3916 | }, 3917 | 3918 | /** 3919 | * Diff the new array with the old 3920 | * and determine the minimum amount of DOM manipulations. 3921 | */ 3922 | diff: function (newCollection, isObject) { 3923 | 3924 | var i, l, item, vm, 3925 | oldIndex, 3926 | targetNext, 3927 | currentNext, 3928 | nextEl, 3929 | ctn = this.container, 3930 | oldVMs = this.oldVMs, 3931 | vms = [] 3932 | 3933 | vms.length = newCollection.length 3934 | 3935 | // first pass, collect new reused and new created 3936 | for (i = 0, l = newCollection.length; i < l; i++) { 3937 | item = newCollection[i] 3938 | if (isObject) { 3939 | item.$index = i 3940 | if (item.__emitter__ && item.__emitter__[this.identifier]) { 3941 | // this piece of data is being reused. 3942 | // record its final position in reused vms 3943 | item.$reused = true 3944 | } else { 3945 | vms[i] = this.build(item, i, isObject) 3946 | } 3947 | } else { 3948 | // we can't attach an identifier to primitive values 3949 | // so have to do an indexOf... 3950 | oldIndex = indexOf(oldVMs, item) 3951 | if (oldIndex > -1) { 3952 | // record the position on the existing vm 3953 | oldVMs[oldIndex].$reused = true 3954 | oldVMs[oldIndex].$data.$index = i 3955 | } else { 3956 | vms[i] = this.build(item, i, isObject) 3957 | } 3958 | } 3959 | } 3960 | 3961 | // second pass, collect old reused and destroy unused 3962 | for (i = 0, l = oldVMs.length; i < l; i++) { 3963 | vm = oldVMs[i] 3964 | item = this.arg 3965 | ? vm.$data[this.arg] 3966 | : vm.$data 3967 | if (item.$reused) { 3968 | vm.$reused = true 3969 | delete item.$reused 3970 | } 3971 | if (vm.$reused) { 3972 | // update the index to latest 3973 | vm.$index = item.$index 3974 | // the item could have had a new key 3975 | if (item.$key && item.$key !== vm.$key) { 3976 | vm.$key = item.$key 3977 | } 3978 | vms[vm.$index] = vm 3979 | } else { 3980 | // this one can be destroyed. 3981 | if (item.__emitter__) { 3982 | delete item.__emitter__[this.identifier] 3983 | } 3984 | vm.$destroy() 3985 | } 3986 | } 3987 | 3988 | // final pass, move/insert DOM elements 3989 | i = vms.length 3990 | while (i--) { 3991 | vm = vms[i] 3992 | item = vm.$data 3993 | targetNext = vms[i + 1] 3994 | if (vm.$reused) { 3995 | nextEl = vm.$el.nextSibling 3996 | // destroyed VMs' element might still be in the DOM 3997 | // due to transitions 3998 | while (!nextEl.vue_vm && nextEl !== this.ref) { 3999 | nextEl = nextEl.nextSibling 4000 | } 4001 | currentNext = nextEl.vue_vm 4002 | if (currentNext !== targetNext) { 4003 | if (!targetNext) { 4004 | ctn.insertBefore(vm.$el, this.ref) 4005 | } else { 4006 | nextEl = targetNext.$el 4007 | // new VMs' element might not be in the DOM yet 4008 | // due to transitions 4009 | while (!nextEl.parentNode) { 4010 | targetNext = vms[nextEl.vue_vm.$index + 1] 4011 | nextEl = targetNext 4012 | ? targetNext.$el 4013 | : this.ref 4014 | } 4015 | ctn.insertBefore(vm.$el, nextEl) 4016 | } 4017 | } 4018 | delete vm.$reused 4019 | delete item.$index 4020 | delete item.$key 4021 | } else { // a new vm 4022 | vm.$before(targetNext ? targetNext.$el : this.ref) 4023 | } 4024 | } 4025 | 4026 | return vms 4027 | }, 4028 | 4029 | build: function (data, index, isObject) { 4030 | 4031 | // wrap non-object values 4032 | var raw, alias, 4033 | wrap = !isObject || this.arg 4034 | if (wrap) { 4035 | raw = data 4036 | alias = this.arg || '$value' 4037 | data = {} 4038 | data[alias] = raw 4039 | } 4040 | data.$index = index 4041 | 4042 | var el = this.el.cloneNode(true), 4043 | Ctor = this.compiler.resolveComponent(el, data), 4044 | vm = new Ctor({ 4045 | el: el, 4046 | data: data, 4047 | parent: this.vm, 4048 | compilerOptions: { 4049 | repeat: true, 4050 | expCache: this.expCache 4051 | } 4052 | }) 4053 | 4054 | if (isObject) { 4055 | // attach an ienumerable identifier to the raw data 4056 | (raw || data).__emitter__[this.identifier] = true 4057 | } 4058 | 4059 | if (wrap) { 4060 | var self = this, 4061 | sync = function (val) { 4062 | self.lock = true 4063 | self.collection.$set(vm.$index, val) 4064 | self.lock = false 4065 | } 4066 | vm.$compiler.observer.on('change:' + alias, sync) 4067 | } 4068 | 4069 | return vm 4070 | 4071 | }, 4072 | 4073 | unbind: function () { 4074 | if (this.childId) { 4075 | delete this.vm.$[this.childId] 4076 | } 4077 | if (this.vms) { 4078 | var i = this.vms.length 4079 | while (i--) { 4080 | this.vms[i].$destroy() 4081 | } 4082 | } 4083 | } 4084 | } 4085 | 4086 | // Helpers -------------------------------------------------------------------- 4087 | 4088 | /** 4089 | * Find an object or a wrapped data object 4090 | * from an Array 4091 | */ 4092 | function indexOf (vms, obj) { 4093 | for (var vm, i = 0, l = vms.length; i < l; i++) { 4094 | vm = vms[i] 4095 | if (!vm.$reused && vm.$value === obj) { 4096 | return i 4097 | } 4098 | } 4099 | return -1 4100 | } 4101 | }); 4102 | require.register("vue/src/directives/on.js", function(exports, require, module){ 4103 | var utils = require('../utils') 4104 | 4105 | /** 4106 | * Binding for event listeners 4107 | */ 4108 | module.exports = { 4109 | 4110 | isFn: true, 4111 | 4112 | bind: function () { 4113 | this.context = this.binding.isExp 4114 | ? this.vm 4115 | : this.binding.compiler.vm 4116 | }, 4117 | 4118 | update: function (handler) { 4119 | if (typeof handler !== 'function') { 4120 | utils.warn('Directive "v-on:' + this.expression + '" expects a method.') 4121 | return 4122 | } 4123 | this._unbind() 4124 | var vm = this.vm, 4125 | context = this.context 4126 | this.handler = function (e) { 4127 | e.targetVM = vm 4128 | context.$event = e 4129 | var res = handler.call(context, e) 4130 | context.$event = null 4131 | return res 4132 | } 4133 | this.el.addEventListener(this.arg, this.handler) 4134 | }, 4135 | 4136 | unbind: function () { 4137 | this.el.removeEventListener(this.arg, this.handler) 4138 | } 4139 | } 4140 | }); 4141 | require.register("vue/src/directives/model.js", function(exports, require, module){ 4142 | var utils = require('../utils'), 4143 | isIE9 = navigator.userAgent.indexOf('MSIE 9.0') > 0, 4144 | filter = [].filter 4145 | 4146 | /** 4147 | * Returns an array of values from a multiple select 4148 | */ 4149 | function getMultipleSelectOptions (select) { 4150 | return filter 4151 | .call(select.options, function (option) { 4152 | return option.selected 4153 | }) 4154 | .map(function (option) { 4155 | return option.value || option.text 4156 | }) 4157 | } 4158 | 4159 | /** 4160 | * Two-way binding for form input elements 4161 | */ 4162 | module.exports = { 4163 | 4164 | bind: function () { 4165 | 4166 | var self = this, 4167 | el = self.el, 4168 | type = el.type, 4169 | tag = el.tagName 4170 | 4171 | self.lock = false 4172 | self.ownerVM = self.binding.compiler.vm 4173 | 4174 | // determine what event to listen to 4175 | self.event = 4176 | (self.compiler.options.lazy || 4177 | tag === 'SELECT' || 4178 | type === 'checkbox' || type === 'radio') 4179 | ? 'change' 4180 | : 'input' 4181 | 4182 | // determine the attribute to change when updating 4183 | self.attr = type === 'checkbox' 4184 | ? 'checked' 4185 | : (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') 4186 | ? 'value' 4187 | : 'innerHTML' 4188 | 4189 | // select[multiple] support 4190 | if(tag === 'SELECT' && el.hasAttribute('multiple')) { 4191 | this.multi = true 4192 | } 4193 | 4194 | var compositionLock = false 4195 | self.cLock = function () { 4196 | compositionLock = true 4197 | } 4198 | self.cUnlock = function () { 4199 | compositionLock = false 4200 | } 4201 | el.addEventListener('compositionstart', this.cLock) 4202 | el.addEventListener('compositionend', this.cUnlock) 4203 | 4204 | // attach listener 4205 | self.set = self.filters 4206 | ? function () { 4207 | if (compositionLock) return 4208 | // if this directive has filters 4209 | // we need to let the vm.$set trigger 4210 | // update() so filters are applied. 4211 | // therefore we have to record cursor position 4212 | // so that after vm.$set changes the input 4213 | // value we can put the cursor back at where it is 4214 | var cursorPos 4215 | try { cursorPos = el.selectionStart } catch (e) {} 4216 | 4217 | self._set() 4218 | 4219 | // since updates are async 4220 | // we need to reset cursor position async too 4221 | utils.nextTick(function () { 4222 | if (cursorPos !== undefined) { 4223 | el.setSelectionRange(cursorPos, cursorPos) 4224 | } 4225 | }) 4226 | } 4227 | : function () { 4228 | if (compositionLock) return 4229 | // no filters, don't let it trigger update() 4230 | self.lock = true 4231 | 4232 | self._set() 4233 | 4234 | utils.nextTick(function () { 4235 | self.lock = false 4236 | }) 4237 | } 4238 | el.addEventListener(self.event, self.set) 4239 | 4240 | // fix shit for IE9 4241 | // since it doesn't fire input on backspace / del / cut 4242 | if (isIE9) { 4243 | self.onCut = function () { 4244 | // cut event fires before the value actually changes 4245 | utils.nextTick(function () { 4246 | self.set() 4247 | }) 4248 | } 4249 | self.onDel = function (e) { 4250 | if (e.keyCode === 46 || e.keyCode === 8) { 4251 | self.set() 4252 | } 4253 | } 4254 | el.addEventListener('cut', self.onCut) 4255 | el.addEventListener('keyup', self.onDel) 4256 | } 4257 | }, 4258 | 4259 | _set: function () { 4260 | this.ownerVM.$set( 4261 | this.key, this.multi 4262 | ? getMultipleSelectOptions(this.el) 4263 | : this.el[this.attr] 4264 | ) 4265 | }, 4266 | 4267 | update: function (value, init) { 4268 | /* jshint eqeqeq: false */ 4269 | // sync back inline value if initial data is undefined 4270 | if (init && value === undefined) { 4271 | return this._set() 4272 | } 4273 | if (this.lock) return 4274 | var el = this.el 4275 | if (el.tagName === 'SELECT') { // select dropdown 4276 | el.selectedIndex = -1 4277 | if(this.multi && Array.isArray(value)) { 4278 | value.forEach(this.updateSelect, this) 4279 | } else { 4280 | this.updateSelect(value) 4281 | } 4282 | } else if (el.type === 'radio') { // radio button 4283 | el.checked = value == el.value 4284 | } else if (el.type === 'checkbox') { // checkbox 4285 | el.checked = !!value 4286 | } else { 4287 | el[this.attr] = utils.guard(value) 4288 | } 4289 | }, 4290 | 4291 | updateSelect: function (value) { 4292 | /* jshint eqeqeq: false */ 4293 | // setting