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