1 // ========================================================================== 2 // Papercube.ViewController 3 // 4 // License: PaperCube is open source software released under 5 // the MIT License (see license.js) 6 // ========================================================================== 7 8 require('core'); 9 10 /** @class 11 12 This controller controls what view is currently being shown. There are 13 two dimensions, the type of object being visualized (ie. Authors or Papers) and 14 the type of view that we're showing (ie. CircleView or TreeView). 15 16 @extends SC.Object 17 @author Peter Bergstrom 18 @version 1.0 19 @copyright 2008-2009 Peter Bergström. 20 @static 21 */ 22 Papercube.viewController = SC.Object.create( 23 /** @scope Papercube.viewController */ { 24 25 /** 26 Available viewing modes. 27 28 Values ["Papers", "Authors"] 29 30 @property {Array} 31 */ 32 viewingModes: ["Papers", "Authors"], 33 34 /** 35 Available views for viewing modes. 36 37 Values 38 39 { 40 "Authors": ["Author Detail", "Papers", "Collaborators", "Author's Cites"], 41 "Papers": ["Paper Detail", "Circle View", "Tree Map", "Per Year", "Paper Graph"] 42 }, 43 44 @property {Object} 45 */ 46 viewChoices: 47 { 48 "Authors": ["Author Detail", "Papers", "Collaborators", "Author's Cites"], 49 "Papers": ["Paper Detail", "Circle View", "Tree Map", "Per Year", "Paper Graph"] 50 }, 51 52 /** 53 Default viewing mode is Papers. 54 55 @property {String} 56 @default 'Papers' 57 */ 58 viewingMode: "Papers", 59 60 /** 61 Default viewChoice is 'Paper Detail'. 62 63 @property {String} 64 @default 'Paper Detail' 65 */ 66 viewChoice: "Paper Detail", 67 68 /** 69 Default paperViewChoice is 'Paper Detail'. 70 71 @property {String} 72 @default 'Paper Detail' 73 */ 74 paperViewChoice: 'Paper Detail', 75 76 /** 77 Default authorViewChoice is 'Author Detail'. 78 79 @property {String} 80 @default 'Author Detail' 81 */ 82 authorViewChoice: 'Author Detail', 83 84 /** 85 Visibilty relation hash. 86 87 Values 88 89 { 90 // Papers 91 "Circle View": "circleView", 92 "Per Year": "papersPerYearView", 93 "Paper Detail": "paperStatView", 94 "Tree Map": "paperTreeView", 95 "Paper Graph": "paperGraphView", 96 97 // Authors 98 "Papers": "authorPaperView", 99 "Collaborators": "collaboratorView", 100 "Author's Cites": "citeRefView", 101 "Author Detail": "authorStatView" 102 } 103 104 @property {Object} 105 */ 106 _viewVisibilities: 107 { 108 // Papers 109 "Circle View": "circleView", 110 "Per Year": "papersPerYearView", 111 "Paper Detail": "paperStatView", 112 "Tree Map": "paperTreeView", 113 "Paper Graph": "paperGraphView", 114 115 // Authors 116 "Papers": "authorPaperView", 117 "Collaborators": "collaboratorView", 118 "Author's Cites": "citeRefView", 119 "Author Detail": "authorStatView" 120 }, 121 122 /** 123 Available view directions. 124 125 Values ["References", "Citations"] 126 127 @property {Array} 128 */ 129 viewDirectionChoices: ["References", "Citations"], 130 131 /** 132 Default view direction. 133 134 @property {String} 135 @default 'References' 136 */ 137 viewDirection: "References", 138 139 /** 140 circleView's visibility. 141 142 Paper view. 143 144 @property {Boolean} 145 @default NO 146 */ 147 circleViewShowing: NO, 148 149 /** 150 paperTreeView's visibility. 151 152 Paper view. 153 154 @property {Boolean} 155 @default NO 156 */ 157 paperTreeViewShowing: NO, 158 159 /** 160 papersPerYearView's visibility. 161 162 Paper view. 163 164 @property {Boolean} 165 @default NO 166 */ 167 papersPerYearViewShowing: NO, 168 169 /** 170 paperStatView's visibility. 171 172 Paper view. 173 174 @property {Boolean} 175 @default NO 176 */ 177 paperStatViewShowing: NO, 178 179 /** 180 paperGraphView's visibility. 181 182 Paper view. 183 184 @property {Boolean} 185 @default NO 186 */ 187 paperGraphViewShowing: NO, 188 189 /** 190 authorPaperView's visibility. 191 192 Author view. 193 194 @property {Boolean} 195 @default NO 196 */ 197 authorPaperViewShowing: NO, 198 199 /** 200 authorStatView's visibility. 201 202 Author view. 203 204 @property {Boolean} 205 @default NO 206 */ 207 authorStatViewShowing: NO, 208 209 /** 210 collaboratorView's visibility. 211 212 Author view. 213 214 @property {Boolean} 215 @default NO 216 */ 217 collaboratorViewShowing: NO, 218 219 /** 220 citeRefView's visibility. 221 222 Author view. 223 224 @property {Boolean} 225 @default NO 226 */ 227 citeRefViewShowing: NO, 228 229 /** 230 The intro view visibility. 231 232 @property {Boolean} 233 @default NO 234 */ 235 showIntroView: NO, 236 237 /** 238 Checkbox value. Default YES so that it never is shown again. 239 240 @property {Boolean} 241 @default YES 242 */ 243 shouldNotShowAgain: YES, 244 245 /** 246 savedPanelView's visibility. 247 248 @property {Boolean} 249 @default NO 250 */ 251 savedPanelViewShowing: NO, 252 253 254 255 /** 256 Now showing property for the tab view. 257 258 @property {String} 259 @default 'noContent' 260 */ 261 nowShowing: 'noContent', 262 263 /** 264 Saved papers or authors. Saved documents are only per session at the moment. 265 266 Saved in the format: 267 268 {type: "Paper", guid: 'PAPER-1233'} 269 {type: "Author", guid: 'AUTHOR-1233'} 270 271 @property {Array} 272 */ 273 _saved: [], 274 275 276 /** 277 The guids of the items saved. This is kept around so that things aren't saved more than once. 278 279 @property {Object} 280 */ 281 _savedGuids: {}, 282 283 284 /** 285 The content array for the savedPanelView. 286 287 @property {Array} 288 */ 289 savedContent: [], 290 291 292 /** 293 Show the video if the cookie is not written. 294 */ 295 showVideo: function() { 296 297 var nameEQ = 'papercubevideo='; 298 var ca = document.cookie.split(';'); 299 var result = ''; 300 for(var i=0;i < ca.length;i++) 301 { 302 var c = ca[i]; 303 while (c.charAt(0)==' ') c = c.substring(1,c.length); 304 if (c.indexOf(nameEQ) == 0) result = c.substring(nameEQ.length,c.length); 305 } 306 307 if(!result) { 308 $('video').innerHTML = '<object width="640" height="360"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=5661651&server=vimeo.com&show_title=1&show_byline=1&show_portrait=0&color=eeeeee&fullscreen=1" /><embed src="http://vimeo.com/moogaloop.swf?clip_id=5661651&server=vimeo.com&show_title=1&show_byline=1&show_portrait=0&color=eeeeee&fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="640" height="360"></embed></object>'; 309 this.set('showIntroView', YES); 310 } 311 }, 312 313 /** 314 Hide the video and write the cookie if needed. 315 */ 316 hideVideo: function() { 317 this.set('showIntroView', NO); 318 $('video').innerHTML = ''; 319 320 if(this.get('shouldNotShowAgain') === YES) { 321 document.cookie = "papercubevideo=1; expires="+(Date.now()+8640000000)+"; path=/"; 322 } 323 }, 324 325 /** 326 Show the savedPanelView. Look at the saved item guids and find the content from the store. 327 */ 328 showSaved: function() 329 { 330 var saved = this._saved; 331 var savedContent = []; 332 333 // Loop through and get the content or collect what guids need to be retrieved from the server. 334 var neededAuthors = []; 335 var neededPapers = []; 336 337 for(var i=0; i<saved.length; i++) 338 { 339 340 var content = null; 341 var guid = saved[i].guid; 342 if(saved[i].type == 'Author') 343 { 344 content = Papercube.Author.find(saved[i].guid); 345 if(!content && !saved[i].retrieving) 346 { 347 neededAuthors.push(guid); 348 saved[i].retrieving = true; 349 } 350 } 351 else 352 { 353 content = Papercube.Paper.find(guid); 354 if(!content && !saved[i].retrieving) 355 { 356 neededPapers.push(guid); 357 saved[i].retrieving = true; 358 } 359 } 360 361 if(content) 362 { 363 savedContent.push(content); 364 } 365 } 366 367 // Call the server to get papers and authors. 368 var callBack = function() { this.doneRetrievingSaved(); }.bind(this); 369 if(neededPapers.length > 0) 370 { 371 Papercube.searchController.set('showRequestSpinner', YES); 372 Papercube.adaptor.getPaperDetails(neededPapers, callBack); 373 } 374 375 if(neededAuthors.length > 0) 376 { 377 Papercube.searchController.set('showRequestSpinner', YES); 378 Papercube.adaptor.getAuthorDetails(neededAuthors, callBack); 379 } 380 381 // Set the content and show the panel. 382 this.set('savedContent', savedContent); 383 this.set('savedPanelShowing', YES); 384 }, 385 386 /** 387 Hide the savedPanelView. 388 */ 389 hideSaved: function() 390 { 391 this.set('savedPanelShowing', NO); 392 }, 393 394 /** 395 Clear the saved items array and cookie. 396 */ 397 clearSaved: function() 398 { 399 this._saved = []; 400 this._savedGuids = {}; 401 this.set('savedContent', []); 402 this._eraseCookie(); 403 }, 404 405 /** 406 Given the guid and contnet type of an item, save it. 407 408 @param guid {Integer} The item's guid. 409 @param type {string} The item's content type. 410 */ 411 saveObject: function(guid, type) 412 { 413 // If the item hasn't been saved before, save it to the array and the cookie. 414 if(!this._savedGuids[guid]) 415 { 416 this._saved.push({type: type, guid: guid}); 417 this._savedGuids[guid] = 1; 418 this._saveCookie(); 419 } 420 }, 421 422 /** 423 Read the saved items from the 'papercube' cookie. Save the items to the _saved and _savedGuids arrays. 424 425 @returns {Boolean} Returns NO if there is an error. 426 */ 427 readSavedItemsCookie: function() 428 { 429 var nameEQ = 'papercube='; 430 var ca = document.cookie.split(';'); 431 var result = ''; 432 for(var i=0;i < ca.length;i++) 433 { 434 var c = ca[i]; 435 while (c.charAt(0)==' ') c = c.substring(1,c.length); 436 if (c.indexOf(nameEQ) == 0) result = c.substring(nameEQ.length,c.length); 437 } 438 439 if(result == '') return NO; 440 441 var saved = eval(unescape(result)); 442 443 this._saved = saved; 444 }, 445 446 /* 447 Write the _saved array to the 'papercube' cookie. 448 */ 449 _saveCookie: function() 450 { 451 document.cookie = "papercube="+escape(this._saved.toJSON())+"; expires="+(Date.now()+8640000000)+"; path=/"; 452 }, 453 454 /* 455 Erase the 'papercube' cookie. 456 */ 457 _eraseCookie: function() 458 { 459 document.cookie = "papercube=; expires=-1; path=/"; 460 }, 461 462 /* 463 Call back function when the saved items have been returned from the server. 464 465 If the saved panel is showing, then update it with the new information. 466 */ 467 doneRetrievingSaved: function() 468 { 469 Papercube.searchController.set('showRequestSpinner', NO); 470 if(this.get('savedPanelShowing')) 471 { 472 this.showSaved(); 473 } 474 }, 475 476 /** 477 History array. 478 479 Each item is saved in the following format: 480 481 { 482 guid: guid, 483 type: type, 484 label: label, 485 viewChoice: this.get('viewChoice'), 486 viewDirection: this.get('viewDirection'), 487 viewingMode: this.get('viewingMode') 488 } 489 490 @property {Array} 491 */ 492 _history: [], 493 494 /** 495 The current index into the history array. 496 497 @property {Integer} 498 */ 499 _historyIdx: -1, 500 501 /** 502 503 The timer for the back button. 504 505 @property 506 @type {SC.Timer} 507 */ 508 _timer: null, 509 510 /** 511 Add to the history. 512 513 @param guid {Integer} The item's guid. 514 @param type {string} The item's content type. 515 @param label {string} The label of the item. 516 */ 517 addToHistory: function(guid, type, label) 518 { 519 var history = this._history.splice(0, this._historyIdx+1); 520 521 var thisHistory = 522 { 523 guid: guid, 524 type: type, 525 label: label, 526 viewChoice: this.get('viewChoice'), 527 viewDirection: this.get('viewDirection'), 528 viewingMode: this.get('viewingMode') 529 }; 530 531 history.push(thisHistory); 532 this._history = history; 533 this._historyIdx++; 534 this._setHistoryLocation(); 535 }, 536 537 /** 538 Show an item in the history based on the index passed in. Read in the 539 history item and set back all the parameters saved. 540 541 @param idx {Integer} The index to go to. 542 */ 543 _showHistoryItem: function(idx) 544 { 545 var thisHistory = this._history[idx]; 546 this.setViewingMode(thisHistory.viewingMode, YES); 547 this.setViewChoice(thisHistory.viewChoice, YES); 548 this.setViewDirection(thisHistory.viewDirection, YES); 549 this.setContentToViewFromGUID(thisHistory.guid, thisHistory.type, YES); 550 this._setHistoryLocation(); 551 }, 552 553 _lastHash: '', 554 555 parseHash: function() { 556 var ret = NO; 557 if(window.location.hash !== '') { 558 var components = window.location.hash.substr(1).split('/'); 559 560 if(components.length === 5) { 561 562 var isAuthor = components[0] == 'Authors'; 563 var isPaper = components[0] == 'Papers'; 564 565 if(isAuthor) { 566 this.setViewingMode(components[0]); 567 } 568 569 var viewChoice = components[1].replace('_', ' '); 570 if(isAuthor && 571 this.viewChoices.Authors.indexOf(viewChoice)) { 572 this.setViewChoice(viewChoice); 573 } else if(isPaper && 574 this.viewChoices.Papers.indexOf(viewChoice)) { 575 this.setViewChoice(viewChoice); 576 } 577 578 if(components[2] == '1') { 579 this.setViewDirection('Citations'); 580 } 581 582 var guid = (isPaper) ? 'PAPER-%@'.fmt(components[3]) 583 : 'AUTHOR-%@'.fmt(components[3]); 584 585 this._lastHash = window.location.hash; 586 if(isPaper) { 587 Papercube.adaptor.getPaperDetails(guid, 588 function(guid) { 589 this.viewPaper(guid); 590 }.bind(this, guid)); 591 } else if(isAuthor) { 592 Papercube.adaptor.getAuthorDetails(guid, 593 function(guid) { 594 this.viewAuthor(guid); 595 }.bind(this, guid)); 596 } 597 ret = YES; 598 } else { 599 window.location.hash = ''; 600 } 601 } 602 setInterval('Papercube.viewController.checkLocation()', 1000); 603 return ret; 604 }, 605 606 /** Set the location. */ 607 _setHistoryLocation: function() { 608 var idx = this._historyIdx; 609 var item = this._history[idx]; 610 611 window.location.hash = this._lastHash = 612 "%@/%@/%@/%@/%@".fmt( 613 item.viewingMode, 614 item.viewChoice.replace(' ', '_'), 615 (item.viewDirection == 'References') ? '0' : '1', 616 (item.type == 'Paper') 617 ? item.guid.substr(6) : item.guid.substr(7), 618 idx 619 ); 620 621 if(item) { 622 document.title = 'PaperCube - ' + 623 item.label.substr(0, 40) +' - '+ 624 item.viewChoice + ' - ' + item.viewingMode; 625 } 626 }, 627 628 /** Check for location change and then chang it.*/ 629 checkLocation: function() { 630 if(this._lastHash != window.location.hash) { 631 var hash = window.location.hash.split('/'); 632 var idx = parseInt(hash[hash.length-1],10); 633 if(idx !== this._historyIdx) { 634 this._historyIdx = idx; 635 this._showHistoryItem(idx); 636 } 637 } 638 }, 639 640 /** 641 Load default view. 642 */ 643 showDefaultView: function() 644 { 645 this.setViewChoice(this.viewChoice, NO); 646 }, 647 648 /** 649 Change viewing direction. 650 651 @param direction {string} the view direction. 652 @param fromHistory {boolean} True if the view direction is set from a history action. 653 */ 654 setViewDirection: function(direction, fromHistory) 655 { 656 if(this.paperForView && !fromHistory) 657 { 658 this.addToHistory(this.paperForView.get('guid'),this.paperForView.get('type'), this.paperForView.get('label')); 659 } 660 this.set('viewDirection', direction); 661 }, 662 663 /** 664 Change views. 665 666 @param viewChoice {string} the view choice. 667 @param fromHistory {boolean} True if the view direction is set from a history action. 668 669 @returns {Boolean} Returns NO if there is a error. 670 */ 671 setViewChoice: function(viewChoice, fromHistory) 672 { 673 674 // You are trying to show a view that does not exist, abort. 675 if(this.viewChoices[this.viewingMode].indexOf(viewChoice) == -1) 676 { 677 console.log("error: '" + viewChoice +"' is not a valid view choice."); 678 return NO; 679 } 680 681 // Hide all views except for the one that should be visible. 682 var visibilities = this._viewVisibilities; 683 for(var key in visibilities) 684 { 685 this.setIfChanged(visibilities[key]+"Showing", (key == viewChoice)); 686 // console.log("hiding " + visibilities[key]); 687 } 688 // console.log("showing " +viewKey); 689 // this.set(viewKey+"Showing", YES); 690 this.set("viewChoice", viewChoice); 691 692 this.set('nowShowing', this._viewVisibilities[viewChoice]); 693 694 // Cache the last view depending on type. 695 if(this.get('viewingMode') == 'Papers') 696 { 697 this.set("paperViewChoice", viewChoice); 698 } 699 if(this.get('viewingMode') == 'Authors') 700 { 701 this.set("authorViewChoice", viewChoice); 702 } 703 if(this.contentForView && !fromHistory) 704 { 705 this.addToHistory(this.contentForView.get('guid'),this.contentForView.get('type'), this.contentForView.get('label')); 706 } 707 }, 708 709 /** 710 Set the viewing mode. 711 712 @param viewMode {string} the view mode. 713 @param fromHistory {boolean} True if the view direction is set from a history action. 714 715 @returns {Boolean} Returns NO if there is a error. 716 */ 717 setViewingMode: function(viewingMode, fromHistory) 718 { 719 // You are trying to show a view that does not exist, abort. 720 if(this.viewingModes.indexOf(viewingMode) == -1) 721 { 722 console.log("error: '" + viewingMode +"' is not a valid view mode."); 723 return NO; 724 } 725 726 this.set("viewingMode", viewingMode); 727 if(this.contentForView && !fromHistory) 728 { 729 this.addToHistory(this.contentForView.get('guid'),this.contentForView.get('type'), this.contentForView.get('label')); 730 } 731 732 if(this.viewChoices[viewingMode].indexOf(this.get('viewChoice')) == -1) 733 { 734 this.setViewChoice(this.viewChoices[viewingMode][0]); 735 } 736 }, 737 738 /** 739 This is the content that you set that your views listen to. 740 741 @property {Paper} 742 */ 743 paperForView: null, 744 745 /** 746 This is the content that you set that your views listen to. 747 748 @property {Author} 749 */ 750 authorForView: null, 751 752 /** 753 This is the type agnostic param that is used for saving. 754 755 @property {Record} 756 */ 757 contentForView: null, 758 759 /** 760 This is the type agnostic param that is used for saving. 761 True if there is no content set yet. If YES, the empty view is shown. 762 763 @property {Boolean} 764 */ 765 noContentForView: YES, 766 767 /** 768 If the contentForView is not null, set noContentForView to YES. 769 770 @observes contentForView 771 */ 772 noContentForViewDidChange: function() 773 { 774 this.set('noContentForView', !this.get('contentForView')); 775 }.observes('contentForView'), 776 777 /** 778 Collect the guids needed to be retrieved for a given view. 779 780 @param rel {string} The paper guid relation array. 781 @param guids {Array} Array of needed guids. 782 783 @returns {Array} {object} Returns a hash containing papers that exist and guids that need to be retrieved. 784 */ 785 collectNeededPaperGUIDs: function(rel, guids) 786 { 787 if(!guids) guids = []; 788 var papers = []; 789 for(var i=0; i<rel.length; i++) 790 { 791 var paper = Papercube.Paper.find(rel[i]); 792 if(!paper) 793 { 794 guids.push(rel[i]); 795 } 796 else 797 { 798 papers.push(paper); 799 } 800 } 801 802 return {papers: papers, guids: guids}; 803 }, 804 805 /** 806 Set the content for the view. 807 808 @param content {Record} The content object. Either an Author or Paper. 809 @param fromHistory {boolean} True if the view direction is set from a history action. 810 */ 811 setContentToView: function(content, fromHistory) 812 { 813 if(content) 814 { 815 // Push on the history stack. 816 if(!fromHistory) 817 { 818 this.addToHistory(content.get('guid'), content.get('type'), content.get('label')); 819 } 820 821 this.set("contentForView", content); 822 823 if(content.get('type') == 'Author') 824 { 825 this.set('authorForView', content); 826 } 827 if(content.get('type') == 'Paper') 828 { 829 this.set('paperForView', content); 830 } 831 } 832 Papercube.canvasController.hideFan(); 833 }, 834 835 /** 836 Set the content for the view form a guid. 837 838 @param guid {string} The guid of the content that you want to pass to setContentToView. 839 @param type {string} The type of the content. Either 'Author' or 'Paper'. 840 @param fromHistory {boolean} True if the view direction is set from a history action. 841 */ 842 setContentToViewFromGUID: function(guid, type, fromHistory) 843 { 844 var content = null; 845 846 // Get the content from the appropriate source. 847 if(type == "Paper") 848 { 849 content = Papercube.Paper.find(guid); 850 } 851 if(type == "Author") 852 { 853 content = Papercube.Author.find(guid); 854 } 855 856 // Set the content! 857 if(content) 858 { 859 this.setContentToView(content, fromHistory); 860 } 861 Papercube.canvasController.hideFan(); 862 }, 863 864 /** 865 View an author. 866 867 @param guid {string} The guid of the content that you want to pass to setContentToView. 868 */ 869 viewAuthor: function(guid) 870 { 871 var author = Papercube.Author.find(guid); 872 if(author) 873 { 874 this.set("viewingMode", 'Authors'); 875 this.setViewChoice(this.authorViewChoice, NO); 876 this.setContentToView(author, NO); 877 } 878 }, 879 880 /** 881 View an paper. 882 883 @param guid {string} The guid of the content that you want to pass to setContentToView. 884 */ 885 viewPaper: function(guid) 886 { 887 var paper = Papercube.Paper.find(guid); 888 if(paper) 889 { 890 this.set("viewingMode", 'Papers'); 891 this.setViewChoice(this.paperViewChoice, NO); 892 this.setContentToView(paper, NO); 893 } 894 } 895 896 }) ; 897