Merge branch 'hotfix/21.56.9' into master
[gitter.git] / public / js / views / app / collaboratorsView.js
blobc3cd4ac9c3b73f2054009ade41577ad0e9f021dc
1 'use strict';
3 var Marionette = require('backbone.marionette');
4 var Backbone = require('backbone');
5 var context = require('gitter-web-client-context');
6 var social = require('../../utils/social');
7 var apiClient = require('../../components/api-client');
8 var template = require('./tmpl/collaboratorsView.hbs');
9 var itemTemplate = require('./tmpl/collaboratorsItemView.hbs');
10 var emptyViewTemplate = require('./tmpl/collaboratorsEmptyView.hbs');
11 var appEvents = require('../../utils/appevents');
12 var collaboratorsModels = require('../../collections/collaborators');
14 require('@gitterhq/styleguide/css/components/buttons.css');
15 require('@gitterhq/styleguide/css/components/links.css');
17 module.exports = (function() {
18   var ItemView = Marionette.ItemView.extend({
19     events: {
20       'submit form': 'inviteUser',
21       'click .js-add': 'addUser'
22     },
24     className: 'welcome-modal__collaborator',
26     template: itemTemplate,
28     initialize: function(options) {
29       this.userModel = options.model;
30       this.stateModel = new Backbone.Model({
31         state: 'initial',
32         emailRequiredUserId: null
33       });
35       this.listenTo(this.userModel, 'change', this.render);
36       this.listenTo(this.stateModel, 'change', this.render);
37     },
39     /**
40      * TODO: deal with non-GitHub users too
41      */
42     inviteGitHubUser: function(data) {
43       var self = this;
44       var state = 'inviting';
46       this.stateModel.set('state', state);
48       return apiClient.room
49         .post('/invites', data)
50         .then(function(invite) {
51           if (invite.email) {
52             self.userModel.set('email', invite.email);
53           }
55           if (invite.status === 'added') {
56             self.stateModel.set('state', 'added');
57           } else if (invite.status === 'invited') {
58             self.stateModel.set('state', 'invited');
59           }
60         })
61         .catch(function(e) {
62           if (e.status === 409) {
63             self.stateModel.set('state', 'fail_409');
64           } else if (e.status === 428) {
65             self.stateModel.set({
66               state: 'email_address_required'
67             });
68           } else {
69             self.stateModel.set('state', 'fail');
70           }
71         });
72     },
74     inviteUser: function() {
75       var email = this.$el.find('.js-invite-email').val();
76       this.userModel.set({ email: email });
77       this.inviteGitHubUser(this.userModel.toJSON());
79       // stop the page reloading
80       return false;
81     },
83     addUser: function() {
84       appEvents.triggerParent('track-event', 'welcome-add-user-click');
86       this.inviteGitHubUser(this.userModel.toJSON(), null);
88       return false;
89     },
91     serializeData: function() {
92       var state = this.stateModel.get('state');
93       var displayName = this.userModel.get('displayName');
94       var email = this.userModel.get('email');
96       var states = {
97         initial: { text: displayName, showAddButton: true },
98         adding: { text: 'Adding…' },
99         added: { text: displayName + ' added' },
100         invited: { text: email ? 'Invited ' + email : 'Invited' },
101         fail: { text: 'Unable to add ' + displayName },
102         fail_409: { text: 'Already invited' },
103         email_address_required: { text: 'Enter ' + displayName + "'s email", showEmailForm: true },
104         inviting: { text: 'Inviting…' }
105       };
107       var data = states[state] || states.initial;
108       data.avatarUrl = this.userModel.get('avatarUrl');
110       return data;
111     }
112   });
114   var EmptyView = Marionette.ItemView.extend({
115     template: emptyViewTemplate,
116     className: 'welcome-modal__no-suggestions',
117     initialize: function(options) {
118       this.model.set('security', options.security);
119       this.model.set('githubType', options.githubType);
120       this.model.set('url', options.url);
121     },
123     serializeData: function() {
124       var data = this.model.toJSON();
125       // FIXME: Just rename it so it doesn't include the `url` module: https://github.com/altano/handlebars-loader/issues/75
126       data.stub = data.url;
128       if (data.githubType === 'ORG') {
129         data.showOrgMessage = true;
130       }
132       if (data.githubType === 'ORG_CHANNEL') {
133         if (data.security === 'INHERITED') {
134           data.showOrgMessage = true;
135         }
136       }
138       if (data.security === 'PUBLIC') {
139         data.isPublic = true;
140       }
142       return data;
143     }
144   });
146   var View = Marionette.CompositeView.extend({
147     childViewContainer: '.js-container',
148     childView: ItemView,
149     emptyView: EmptyView,
151     childViewOptions: function() {
152       if (!this.collection.length) {
153         return {
154           githubType: context.troupe().get('githubType'),
155           security: context.troupe().get('security'),
156           url: context.troupe().get('url')
157         };
158       }
159     },
161     template: template,
163     constructor: function() {
164       //instantiate our collection
165       this.collection = new collaboratorsModels.CollabCollection();
167       //if we should fetch data we should
168       if (this.shouldFetch()) {
169         //If we render initially we will get a flash of the empty view
170         //to avoid that we set hasGotData to signify that we have not yet received any data
171         this.collection.fetch();
172         this.hasGotSomeData = false;
173       }
175       //if we don't need to get some data we should reset the catch
176       else this.hasGotSomeData = true;
177       this.listenTo(
178         this.collection,
179         'sync',
180         function() {
181           //once we get some data we set it to true so we can
182           //once again render
183           this.hasGotSomeData = true;
185           //and call a manual render
186           this.render();
187         },
188         this
189       );
191       //call super()
192       Marionette.CompositeView.prototype.constructor.apply(this, arguments);
193     },
195     initialize: function() {
196       //listen to room permission changes so we can refresh the collection
197       this.listenTo(context.troupe(), 'change:id', this.onRoomChange, this);
198     },
200     events: {
201       'click .js-close': 'dismiss',
202       'click #add-button': 'clickAddButton',
203       'click #share-button': 'clickShareButton'
204     },
206     //when a room changes refresh the collection
207     onRoomChange: function() {
208       //hide the view so we don't see collaborators from previous rooms
209       this.$el.hide();
210       appEvents.trigger('collaboratorsView:hide');
212       //fetch if we need to
213       if (this.shouldFetch()) return this.collection.fetch();
215       //render if we do not
216       this.render();
217     },
219     serializeData: function() {
220       var uri = context.troupe().get('uri');
221       return {
222         isPublic: context.troupe().get('security') === 'PUBLIC',
223         twitterLink: social.generateTwitterShareUrl(uri),
224         facebookLink: social.generateFacebookShareUrl(uri)
225       };
226     },
228     clickAddButton: function() {
229       appEvents.triggerParent('track-event', 'welcome-search-clicked');
230       window.location.href = '#add';
231     },
233     clickShareButton: function() {
234       window.location.href = '#share';
235     },
237     dismiss: function() {
238       this.remove();
239     },
241     //Check if we should fetch data
242     shouldFetch: function() {
243       var roomModel = context.troupe();
244       var roomType = roomModel.get('githubType');
245       var userCount = roomModel.get('userCount');
247       //don't fetch for one-to-one rooms
248       if (roomType === 'ONETOONE') return false;
250       //don't fetch if the user is not an admin
251       if (!context.isTroupeAdmin()) return false;
253       //don't run if we have more than one user
254       if (userCount > 1) return false;
256       //if all else fails fetch some data
257       return true;
258     },
260     //Check if we should render content
261     shouldRender: function() {
262       //if we should fetch data && have have previously
263       //in the app life cycle had some data
264       if (this.shouldFetch() && this.hasGotSomeData) return true;
265     },
267     render: function() {
268       if (this.shouldRender()) {
269         Marionette.CompositeView.prototype.render.apply(this, arguments);
270         this.$el.show();
271         appEvents.trigger('collaboratorsView:show');
272       } else {
273         this.$el.hide();
274         appEvents.trigger('collaboratorsView:hide');
275         return this;
276       }
278       return this;
279     }
280   });
282   return View;
283 })();