1 /**
  2  * Topic component.
  3  * Shows and allows to edit a topic.
  4  * 
  5  * @namespace Alfresco
  6  * @class Alfresco.DiscussionsTopic
  7  */
  8 (function()
  9 {
 10    /**
 11     * YUI Library aliases
 12     */
 13    var Dom = YAHOO.util.Dom,
 14        Event = YAHOO.util.Event,
 15        Element = YAHOO.util.Element;
 16 
 17    /**
 18     * Alfresco Slingshot aliases
 19     */
 20    var $html = Alfresco.util.encodeHTML;
 21 
 22    /**
 23    * Topic constructor.
 24    * 
 25    * @param {String} htmlId The HTML id of the parent element
 26    * @return {Alfresco.TopicView} The new Topic instance
 27    * @constructor
 28    */
 29    Alfresco.DiscussionsTopic = function(htmlId)
 30    {
 31       /* Mandatory properties */
 32       this.name = "Alfresco.DiscussionsTopic";
 33       this.id = htmlId;
 34       
 35       /* Initialise prototype properties */
 36       this.widgets = {};
 37       this.modules = {};
 38       this.tagId =
 39       {
 40          id: 0,
 41          tags: {}
 42       };
 43       
 44       /* Register this component */
 45       Alfresco.util.ComponentManager.register(this);
 46 
 47       /* Load YUI Components */
 48       Alfresco.util.YUILoaderHelper.require(["datasource", "json", "connection", "event", "button", "menu", "editor"], this.onComponentsLoaded, this);
 49      
 50       /* Decoupled event listeners */
 51       YAHOO.Bubbling.on("tagSelected", this.onTagSelected, this);
 52            
 53       return this;
 54    };
 55    
 56    Alfresco.DiscussionsTopic.prototype =
 57    {
 58       /**
 59        * Object container for initialization options
 60        *
 61        * @property options
 62        * @type object
 63        */
 64       options:
 65       {
 66          /**
 67           * Current siteId.
 68           * 
 69           * @property siteId
 70           * @type string
 71           */
 72          siteId: "",
 73        
 74          /**
 75           * Current containerId.
 76           * 
 77           * @property containerId
 78           * @type string
 79           */
 80          containerId: "discussions",
 81 
 82          /**
 83           * Id of the topic to display.
 84           * 
 85           * @property topicId
 86           * @type string
 87           */
 88          topicId: ""
 89 
 90       },
 91      
 92       /**
 93        * Holds the data displayed in this component.
 94        */
 95       topicData: null,
 96       
 97       /**
 98        * Object container for storing YUI widget instances.
 99        * 
100        * @property widgets
101        * @type object
102        */
103       widgets: null,
104       
105       /**
106        * Object container for storing module instances.
107        * 
108        * @property modules
109        * @type object
110        */
111       modules: null,
112       
113       /**
114        * Object literal used to generate unique tag ids
115        * 
116        * @property tagId
117        * @type object
118        */
119       tagId: null,
120       
121       /**
122        * Tells whether an action is currently ongoing.
123        * 
124        * @property busy
125        * @type boolean
126        * @see _setBusy/_releaseBusy
127        */
128       busy: false,
129       
130       /**
131        * Set multiple initialization options at once.
132        *
133        * @method setOptions
134        * @param obj {object} Object literal specifying a set of options
135        */
136       setOptions: function DiscussionsTopic_setOptions(obj)
137       {
138          this.options = YAHOO.lang.merge(this.options, obj);
139          return this;
140       },
141      
142       /**
143        * Set multiple initialization options at once.
144        *
145        * @method setOptions
146        * @param obj {object} Object literal specifying a set of options
147        */
148       setMessages: function DiscussionsTopic_setMessages(obj)
149       {
150          Alfresco.util.addMessages(obj, this.name);
151          return this;
152       },
153      
154       /**
155        * Fired by YUILoaderHelper when required component script files have
156        * been loaded into the browser.
157        *
158        * @method onComponentsLoaded
159        */
160       onComponentsLoaded: function DiscussionsTopic_onComponentsLoaded()
161       {
162          Event.onContentReady(this.id, this.onReady, this, true);
163       },
164    
165   
166       /**
167        * Fired by YUI when parent element is available for scripting.
168        * Component initialisation, including instantiation of YUI widgets and event listener binding.
169        *
170        * @method onReady
171        */
172       onReady: function DiscussionsTopic_onReady()
173       {
174          var me = this;
175          
176          // Hook action events.
177          var fnActionHandlerDiv = function DiscussionsTopic_fnActionHandlerDiv(layer, args)
178          {
179 
180             var owner = YAHOO.Bubbling.getOwnerByTagName(args[1].anchor, "div");
181             if (owner !== null)
182             {
183                var action = "";
184                action = owner.className;
185                if (typeof me[action] == "function")
186                {
187                   me[action].call(me, me.topicData.name);
188                   args[1].stop = true;
189                }
190             }
191             return true;
192          };
193          YAHOO.Bubbling.addDefaultAction("topic-action-link-div", fnActionHandlerDiv);
194          
195          // register tag action handler, which will issue tagSelected bubble events.
196          Alfresco.util.tags.registerTagActionHandler(this);
197           
198          // initialize the mouse over listener
199          Alfresco.util.rollover.registerHandlerFunctions(this.id, this.onTopicElementMouseEntered, this.onTopicElementMouseExited, this);
200           
201          // load the topic data
202          this._loadTopicData();
203       },
204       
205       
206       /**
207        * Loads the topic data and updates the ui.
208        */
209       _loadTopicData: function DiscussionsTopic__loadTopicData()
210       {
211          // ajax request success handler
212          var loadTopicDataSuccess = function DiscussionsTopic__loadTopicData_loadTopicDataSuccess(response)
213          {
214             // set the loaded data
215             var data = response.json.item
216             this.topicData = data;
217             
218             // render the ui
219             this.renderUI();
220             
221             // inform the comment components about the loaded post
222             this._fireTopicDataChangedEvent();
223          };
224          
225          // construct url to call
226          var url = YAHOO.lang.substitute(Alfresco.constants.URL_SERVICECONTEXT + "components/forum/post/site/{site}/{container}/{topicId}",
227          {
228             site : this.options.siteId,
229             container: this.options.containerId,
230             topicId: this.options.topicId
231          });
232          
233          // execute ajax request
234          Alfresco.util.Ajax.request(
235          {
236             url: url,
237             successCallback:
238             {
239                fn: loadTopicDataSuccess,
240                scope: this
241             },
242             failureMessage: this._msg("message.loadtopicdata.failure")
243          });
244       },
245 
246       /**
247        * Renders the UI with the data available in the component.
248        */
249       renderUI: function DiscussionsTopic_renderUI()
250       {   
251          // get the container div
252          var viewDiv = Dom.get(this.id + '-topic-view-div');
253          
254          // render the topic and insert the resulting html
255          var html = this.renderTopic(this.topicData);
256          viewDiv.innerHTML = html;
257          
258          // attach the rollover listeners
259          Alfresco.util.rollover.registerListenersByClassName(this.id, 'topic', 'div');
260       },
261       
262       /**
263        * Renders the topic.
264        * 
265        * @param data {object} the data object containing the topic data
266        * @return {string} html representing the data
267        */
268       renderTopic: function DiscussionsTopic_renderTopic(data)
269       {
270          var html = '';
271           
272          html += '<div id="' + this.id + '-topicview" class="node topic topicview">'
273          
274          // actions
275          html += '<div class="nodeEdit">';
276          if (data.permissions.reply)
277          {
278             html += '<div class="onAddReply"><a href="#" class="topic-action-link-div">' + this._msg("action.reply") + '</a></div>';   
279          }
280          if (data.permissions.edit)
281          {
282             html += '<div class="onEditTopic"><a href="#" class="topic-action-link-div">' + this._msg("action.edit") + '</a></div>';
283          }
284          if (data.permissions['delete'])
285          {
286             html += '<div class="onDeleteTopic"><a href="#" class="topic-action-link-div">' + this._msg("action.delete") + '</a></div>';
287          }
288          html += '</div>';
289   
290          // avatar
291          html += '<div class="authorPicture">' + Alfresco.util.people.generateUserAvatarImg(data.author) + '</div>';
292 
293          // content
294          html += '<div class="nodeContent">';
295          html += '<div class="nodeTitle"><a href="' + Alfresco.util.discussions.getTopicViewPage(this.options.siteId, this.options.containerId, data.name) + '">' + $html(data.title) + '</a> ';
296          if (data.isUpdated)
297          {
298             html += '<span class="theme-color-2 nodeStatus">(' + this._msg("post.updated") + ')</span>';
299          }
300          html += '</div>';
301          
302          html += '<div class="published">';
303          html += '<span class="nodeAttrLabel">' + this._msg("post.createdOn") + ': </span>';
304          html += '<span class="nodeAttrValue">' + Alfresco.util.formatDate(data.createdOn) + '</span>';
305          html += '<span class="separator"> </span>';
306          html += '<span class="nodeAttrLabel">' + this._msg("post.author") + ': </span>';
307          html += '<span class="nodeAttrValue">' + Alfresco.util.people.generateUserLink(data.author) + '</span>';
308          html += '<br />';
309          if (data.lastReplyBy)
310          {
311             html += '<span class="nodeAttrLabel">' + this._msg("post.lastReplyBy") + ': </span>';
312             html += '<span class="nodeAttrValue">' + Alfresco.util.people.generateUserLink(data.lastReplyBy) + '</span>';                  
313             html += '<span class="separator"> </span>';
314             html += '<span class="nodeAttrLabel">' + this._msg("post.lastReplyOn") + ': </span>';
315             html += '<span class="nodeAttrValue">' + Alfresco.util.formatDate(data.lastReplyOn) + '</span>';
316          }
317          else
318          {
319             html += '<span class="nodeAttrLabel">' + this._msg("replies.label") + ': </span>';
320             html += '<span class="nodeAttrValue">' + this._msg("replies.noReplies") + '</span>';                  
321          }
322          html += '</div>';
323              
324          html += '<div class="userLink">' + Alfresco.util.people.generateUserLink(data.author) + ' ' + this._msg("said") + ':</div>';
325          html += '<div class="content yuieditor">' + data.content + '</div>';
326          html += '</div>'
327          // end view
328 
329          // begin footer
330          html += '<div class="nodeFooter">';
331          html += '<span class="nodeAttrLabel replyTo">' + this._msg("replies.label") + ': </span>';
332          html += '<span class="nodeAttrValue">(' + data.totalReplyCount + ')</span>';
333          html += '<span class="separator"> </span>';
334              
335          html += '<span class="nodeAttrLabel tagLabel">' + this._msg("tags.label") +': </span>';
336          if (data.tags.length > 0)
337          {
338             for (var x=0; x < data.tags.length; x++)
339             {
340                if (x > 0)
341                {
342                   html += ", ";
343                }
344                html += Alfresco.util.tags.generateTagLink(this, data.tags[x]);
345             }
346          }
347          else
348          {
349             html += '<span class="nodeAttrValue">' + this._msg("tags.noTags") + '</span>';
350          }
351          html += '</div></div></div>';
352           
353          return html;
354       },
355 
356       /**
357        * Handler for add reply action link
358        */
359       onAddReply: function DiscussionsTopic_onAddReply(htmlId, ownerId, param)
360       {
361          YAHOO.Bubbling.fire('addReplyToPost',
362          {
363             postRef : this.topicData.nodeRef
364          });
365       },
366      
367       /**
368        * Handler for edit topic action link
369        */
370       onEditTopic: function DiscussionsTopic_onEditTopic()
371       {
372          window.location.href = Alfresco.constants.URL_PAGECONTEXT + "site/" + this.options.siteId + "/discussions-createtopic?topicId=" + this.options.topicId;
373       },
374      
375       /**
376        * Handler for delete topic action link
377        */
378       onDeleteTopic: function DiscussionsTopic_onDeleteTopic()
379       {
380          var me = this;
381          Alfresco.util.PopupManager.displayPrompt(
382          {
383             title: this._msg("message.confirm.delete.title"),
384             text: this._msg("message.confirm.delete", $html(this.topicData.title)),
385             buttons: [
386             {
387                text: this._msg("button.delete"),
388                handler: function DiscussionsTopic_onDeleteTopic_delete()
389                {
390                   this.destroy();
391                   me._deleteTopicConfirm.call(me);
392                }
393             },
394             {
395                text: this._msg("button.cancel"),
396                handler: function DiscussionsTopic_onDeleteTopic_cancel()
397                {
398                   this.destroy();
399                },
400                isDefault: true
401             }]
402          });
403       },
404       
405       /**
406        * Delete topic implementation
407        */
408       _deleteTopicConfirm: function DiscussionsTopic__deleteTopicConfirm()
409       {
410          // show busy message
411          if (! this._setBusy(this._msg('message.wait')))
412          {
413             return;
414          }          
415           
416          // ajax request success handler
417          var onDeleted = function onDeleted(response)
418          {
419             var listUrl = YAHOO.lang.substitute(Alfresco.constants.URL_PAGECONTEXT + "site/{site}/discussions-topiclist",
420             {
421                site: this.options.siteId
422             });
423             window.location = listUrl;
424          };
425          
426          // construct the url to call
427          var url = YAHOO.lang.substitute(Alfresco.constants.PROXY_URI + "api/forum/post/site/{site}/{container}/{topicId}?page=discussions-topicview",
428          {
429             site : this.options.siteId,
430             container: this.options.containerId,
431             topicId: encodeURIComponent(this.options.topicId)
432          });
433          
434          // perform the ajax request to delete the topic
435          Alfresco.util.Ajax.request(
436          {
437             url: url,
438             method: "DELETE",
439             responseContentType : "application/json",
440             successMessage: this._msg("message.delete.success"),
441             successCallback:
442             {
443                fn: onDeleted,
444                scope: this
445             },
446             failureMessage: this._msg("message.delete.failure"),
447             failureCallback:
448             {
449                fn: function(response)
450                {
451                   this._releaseBusy();
452                },
453                scope: this
454             }
455          });
456       },
457       
458       /**
459        * Tag selected handler
460        *
461        * @method onTagSelected
462        * @param tagId {string} Tag name.
463        * @param target {HTMLElement} Target element clicked.
464        */
465       onTagSelected: function DiscussionsTopic_onTagSelected(layer, args)
466       {
467          var obj = args[1];
468          if (obj && (obj.tagName !== null))
469          {
470             // construct the topic list url with initial active tag filter
471             var url = YAHOO.lang.substitute(Alfresco.constants.URL_PAGECONTEXT + "site/{site}/discussions-topiclist?filterId={filterId}&filterOwner={filterOwner}&filterData={filterData}",
472             {
473                site: this.options.siteId,
474                filterId: "tag",
475                filterOwner: "Alfresco.TagFilter",
476                filterData: encodeURIComponent(obj.tagName)
477             });
478 
479             window.location = url;
480          }
481       },
482 
483       /**
484        * Loads the edit form.
485        */
486       _loadEditForm: function DiscussionsTopic__loadEditForm()
487       {  
488          // Load the UI template from the server
489          Alfresco.util.Ajax.request(
490          {
491             url: Alfresco.constants.URL_SERVICECONTEXT + "modules/discussions/topic/edit-topic",
492             dataObj:
493             {
494                htmlid: this.id + "-form"
495             },
496             successCallback:
497             {
498                fn: this.onEditFormLoaded,
499                scope: this
500             },
501             failureMessage: this._msg("message.loadeditform.failure")
502          });
503       },
504 
505       /**
506        * Event callback when dialog template has been loaded
507        *
508        * @method onFormLoaded
509        * @param response {object} Server response from load template XHR request
510        */
511       onEditFormLoaded: function DiscussionsTopic_onEditFormLoaded(response)
512       {
513          // id to use for the form
514          var formId = this.id + "-form";
515           
516          // use the already loaded data
517          var data = this.topicData;
518           
519          // find the edit div to populate
520          var editDiv = Dom.get(this.id + "-topic-edit-div");
521          
522          // insert the html
523          editDiv.innerHTML = response.serverResponse.responseText;
524          
525          // insert current values into the form
526          var actionUrl = YAHOO.lang.substitute(Alfresco.constants.PROXY_URI + "api/forum/post/site/{site}/{container}/{topicId}",
527          {
528             site: this.options.siteId,
529             container : this.options.containerId,
530             topicId: this.options.topicId
531          });
532          Dom.get(formId + "-form").setAttribute("action", actionUrl);
533          Dom.get(formId + "-site").setAttribute("value", this.options.siteId);
534          Dom.get(formId + "-container").setAttribute("value", this.options.containerId);
535          Dom.get(formId + "-title").setAttribute("value", data.title);
536          Dom.get(formId + "-content").value = data.content;
537 
538          // and finally register the form handling
539          this._registerEditTopicForm(data, formId);
540       },
541 
542       /**
543        * Registers the form logic
544        */
545       _registerEditTopicForm: function DiscussionsTopic__registerEditTopicForm(data, formId)
546       {
547          // add the tags that are already set on the post
548          if (this.modules.tagLibrary == undefined)
549          {
550             this.modules.tagLibrary = new Alfresco.module.TagLibrary(formId);
551             this.modules.tagLibrary.setOptions(
552             {
553                siteId: this.options.siteId
554             });
555          }
556          this.modules.tagLibrary.setTags(this.topicData.tags);
557          
558          // register the okButton
559          this.widgets.okButton = new YAHOO.widget.Button(formId + "-submit",
560          {
561             type: "submit"
562          });
563          
564          // register the cancel button
565          this.widgets.cancelButton = new YAHOO.widget.Button(formId + "-cancel",
566          {
567             type: "button"
568          });
569          this.widgets.cancelButton.subscribe("click", this.onEditFormCancelButtonClick, this, true);
570          
571          // instantiate the simple editor we use for the form
572          this.widgets.editor = new YAHOO.widget.SimpleEditor(formId + '-content',
573          {
574              height: '180px',
575              width: '700px',
576              dompath: false, //Turns on the bar at the bottom
577              animate: false, //Animates the opening, closing and moving of Editor windows
578              toolbar:  Alfresco.util.editor.getTextOnlyToolbarConfig(this._msg)
579          });
580          this.widgets.editor.addPageUnloadBehaviour(this._msg("message.unsavedChanges.reply"));
581          this.widgets.editor.render();
582          
583          // create the form that does the validation/submit
584          var editForm = new Alfresco.forms.Form(formId + "-form");
585          editForm.setShowSubmitStateDynamically(true, false);
586          editForm.setSubmitElements(this.widgets.okButton);
587          editForm.setAjaxSubmitMethod(Alfresco.util.Ajax.PUT);
588          editForm.setAJAXSubmit(true,
589          {
590             successMessage: this._msg("message.savetopic.success"),
591             successCallback:
592             {
593                fn: this.onEditFormSubmitSuccess,
594                scope: this
595             },
596             failureMessage: this._msg("message.savetopic.failure"),
597             failureCallback:
598             {
599                fn: this.onEditFormSubmitFailure,
600                scope: this
601             }
602          });
603          editForm.setSubmitAsJSON(true);
604          editForm.doBeforeFormSubmit =
605          {
606             fn: function(form, obj)
607             {   
608                // disable the buttons
609                this.widgets.okButton.set("disabled", true);
610                this.widgets.cancelButton.set("disabled", true);
611                
612                //Put the HTML back into the text area
613                this.widgets.editor.saveHTML();
614                
615                // update the tags set in the form
616                this.modules.tagLibrary.updateForm(formId + "-form", "tags");
617                
618                // show a wait message
619                this.widgets.feedbackMessage = Alfresco.util.PopupManager.displayMessage(
620                {
621                   text: Alfresco.util.message(this._msg("message.submitting")),
622                   spanClass: "wait",
623                   displayTime: 0
624                });
625             },
626             scope: this
627          };
628          
629          this.modules.tagLibrary.initialize(editForm);
630          editForm.init();
631          
632          // show the form and hide the view
633          this._showEditView();
634          
635          // TODO: disabled as it does not work correctly on IE. The focus is set
636          // but hitting tab moves the focus to the focus to the address bar instead
637          // of the editor
638          // focus the title text field
639          //Dom.get(formId + "-title").focus();
640       },
641       
642       /**
643        * Edit form submit success handler
644        */
645       onEditFormSubmitSuccess: function DiscussionsTopic_onEditFormSubmitSuccess(response, object)
646       {
647          // remove busy message
648          this._releaseBusy();
649          
650          // the response contains the new data for the comment. Render the comment html
651          // and insert it into the view element
652          this.topicData = response.json.item;
653          this.renderUI();
654             
655          // hide the form and show the ui
656          this._hideEditView();
657             
658          // inform the replies object about the update
659          this._fireTopicDataChangedEvent();
660       },
661       
662       /**
663        * Edit form submit failure handler
664        */
665       onEditFormSubmitFailure: function DiscussionsTopic_onEditFormSubmitFailure(response, object)
666       {
667          // remove busy message
668          this._releaseBusy();
669           
670          // enable the buttons
671          this.widgets.okButton.set("disabled", false);
672          this.widgets.cancelButton.set("disabled", false);
673       },
674       
675       /**
676        * Edit form cancel button click handler
677        */
678       onEditFormCancelButtonClick: function(type, args)
679       {
680           this._hideEditView();
681       },
682       
683       /**
684        * Hides the form and displays the view
685        */
686       _hideEditView: function()
687       {
688          var editDiv = Dom.get(this.id + "-topic-edit-div");
689          var viewDiv = Dom.get(this.id + "-topic-view-div");
690          Dom.addClass(editDiv, "hidden");
691          Dom.removeClass(viewDiv, "hidden");
692          editDiv.innerHTML = '';
693       },
694       
695       /**
696        * Hides the view and displays the form
697        */
698       _showEditView: function()
699       {
700          var editDiv = Dom.get(this.id + "-topic-edit-div");
701          var viewDiv = Dom.get(this.id + "-topic-view-div");
702          Dom.addClass(viewDiv, "hidden");
703          Dom.removeClass(editDiv, "hidden");
704       },
705 
706       /**
707        * Called when the mouse enters into the topic div
708        */
709       onTopicElementMouseEntered: function DiscussionsTopicList_onTopicElementMouseEntered(layer, args)
710       {
711          // make sure the user sees at least one action, otherwise we won't highlight
712          var permissions = this.topicData.permissions;
713          if (!(permissions.edit || permissions["delete"]))
714          {
715             return;
716          } 
717          
718          Dom.addClass(args[1].target, 'over');
719       },
720      
721       /**
722        * Called whenever the mouse exits the topic div
723        */
724       onTopicElementMouseExited: function DiscussionsTopicList_onTopicElementMouseExited(layer, args)
725       {
726          Dom.removeClass(args[1].target, 'over');
727       },
728 
729       /**
730        * Fires a topic data changed bubble event
731        */
732       _fireTopicDataChangedEvent: function DiscussionsTopicList__fireTopicDataChangedEvent()
733       {
734          var eventData =
735          {
736             topicRef: this.topicData.nodeRef,
737             topicTitle: this.topicData.title,
738             topicId: this.topicData.name
739          };
740          YAHOO.Bubbling.fire("topicDataChanged", eventData);
741       },
742       
743       /**
744        * Displays the provided busyMessage but only in case
745        * the component isn't busy set.
746        * 
747        * @return true if the busy state was set, false if the component is already busy
748        */
749       _setBusy: function DiscussionsTopic__setBusy(busyMessage)
750       {
751          if (this.busy)
752          {
753             return false;
754          }
755          this.busy = true;
756          this.widgets.busyMessage = Alfresco.util.PopupManager.displayMessage(
757          {
758             text: busyMessage,
759             spanClass: "wait",
760             displayTime: 0
761          });
762          return true;
763       },
764       
765       /**
766        * Removes the busy message and marks the component as non-busy
767        */
768       _releaseBusy: function DiscussionsTopic__releaseBusy()
769       {
770          if (this.busy)
771          {
772             this.widgets.busyMessage.destroy();
773             this.busy = false;
774             return true;
775          }
776          else
777          {
778             return false;
779          }
780       },
781 
782       /**
783        * Gets a custom message
784        *
785        * @method _msg
786        * @param messageId {string} The messageId to retrieve
787        * @return {string} The custom message
788        * @private
789        */
790       _msg: function DiscussionsTopic_msg(messageId)
791       {
792          return Alfresco.util.message.call(this, messageId, "Alfresco.DiscussionsTopic", Array.prototype.slice.call(arguments).slice(1));
793       }
794    };
795 })();
796