Merge lp:~danilo/launchpad/bugtask-index-portlet-setup into lp:launchpad

Proposed by Данило Шеган
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: 12391
Proposed branch: lp:~danilo/launchpad/bugtask-index-portlet-setup
Merge into: lp:launchpad
Diff against target: 2350 lines (+1161/-1113)
4 files modified
lib/lp/bugs/javascript/bugtask_index.js (+4/-1109)
lib/lp/bugs/javascript/bugtask_index_portlets.js (+1152/-0)
lib/lp/bugs/templates/bug-portlet-subscribers.pt (+3/-3)
lib/lp/bugs/templates/bugtask-index.pt (+2/-1)
To merge this branch: bzr merge lp:~danilo/launchpad/bugtask-index-portlet-setup
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+49818@code.launchpad.net

Commit message

[r=gmb][no-qa] Split portlet setup for subscriber widgets from bugtask-index.js.

Description of the change

= Split bugtask-index.js into two files =

Warning: this is a branch with a huge diff, but it's a move of ~1100 lines of JS from bugtask_index.js to a new bugtask_index_portlets.js. Changes other than the move are minimal.

At the moment, bugtask-index.js is a behemoth of >2k lines of JS, doing many unrelated things:
 - handling duplication
 - handling branch linking
 - handling "me too" functionality
 - handling subscription portlets

The idea is to eventually split it all up by functionality, but a first step is to start with setup_portlet_handlers and move that all out into a separate file. That includes duplication and subscribing portlets set-up.

Future branches will split duplication from subscription stuff.

== Pre-implementation notes ==

Talked this over with Graham who suggested starting with setup_portlet_handlers.

== Implementation details ==

Only two functions are namespaced using lp.bugs.bugtask_index.portlets, because they are the actual entry points. If it is agreed to namespace all the other methods, I'd kind of prefer to do it in a separate branch (to make it easier to bisect if any problems).

Rest of it is a simple code move from bugtask-index.js to bugtask-index-portlets.js.

Fixes in other parts of the tree are due to failing windmill tests.

I am not sure how to best figure out what is the minimum list of required modules for a certain module (I just copied the list over from bugtask_index.js), so if a reviewer has any suggestions, I'd be happy to hear them out.

== Tests ==

bin/test -cvvm lp.bugs --layer=WindmillLayer

== Demo and Q/A ==

Go to eg. https://bugs.launchpad.dev/tomcat/+bug/2 and try setting a bug as a duplicate of another bug (say number 3), subscribing and unsubscribing from a bug. Nothing should change compared to the existing behaviour.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/templates/bug-portlet-subscribers.pt
  lib/lp/bugs/javascript/bugtask_index_portlets.js
  lib/lp/bugs/javascript/bugtask_index.js

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

1184 +var use_advanced_subscriptions = false;

ha, so here's an interesting thing. that should be
`var namespace.use_advanced_subscriptions = false;`. i guess that it's
never been a problem because our code doesn't check for
namespace.use_advanced_subscriptions being undefined. you can get rid of
the xxx above this too since rob and i agreed it wasn't a bug after all.

you're also going to need to update the setting of
y.lp.bugs.bugtask_index.use_advanced_subscriptions in bugtask-index.pt
to make sure it uses the new namespace, otherwise all of the advanced
subscriptions functionality is going to break.

You can double-check the new functionality by running the following query and then checking that the "Subscribe / Edit subscription" link behaves like it does for you on production:

`insert into featureflag (scope, priority, flag, value, date_modified) values ('default', 1, 'malone.advanced-subscriptions.enabled', 'on', now() at time zone 'UTC');`

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/javascript/bugtask_index.js'
--- lib/lp/bugs/javascript/bugtask_index.js 2011-02-10 09:52:06 +0000
+++ lib/lp/bugs/javascript/bugtask_index.js 2011-02-15 18:19:47 +0000
@@ -39,125 +39,9 @@
39var privacy_spinner;39var privacy_spinner;
40var link_branch_link;40var link_branch_link;
4141
42// The set of subscriber CSS IDs as a JSON struct.
43var subscriber_ids;
44
45// A boolean telling us whether advanced subscription features are to be
46// used or not.
47// XXX 2011-01-14 gmb bug=702859:
48// We need to expose feature flags via the API to avoid this kind of
49// thing.
50var use_advanced_subscriptions = false;
51var subscription_labels = Y.lp.bugs.subscriber.subscription_labels;
52
53/*
54 * An object representing the bugtask subscribers portlet.
55 *
56 * Since the portlet loads via XHR and inline subscribing
57 * depends on that portlet being loaded, setup a custom
58 * event object, to provide a hook for initializing subscription
59 * link callbacks after custom events.
60 */
61var PortletTarget = function() {};
62Y.augment(PortletTarget, Y.Event.Target);
63namespace.portlet = new PortletTarget();
64
65function setup_portlet_handlers() {
66 namespace.portlet.subscribe('bugs:portletloaded', function() {
67 load_subscriber_ids();
68 });
69 namespace.portlet.subscribe('bugs:dupeportletloaded', function() {
70 setup_unsubscribe_icon_handlers();
71 });
72 /*
73 * If the subscribers portlet fails to load, clear any
74 * click handlers, so the normal subscribe page can be reached.
75 */
76 namespace.portlet.subscribe('bugs:portletloadfailed', function(handlers) {
77 if (Y.Lang.isArray(handlers)) {
78 var click_handler = handlers[0];
79 click_handler.detach();
80 }
81 });
82 /* If the dupe subscribers portlet fails to load,
83 * be sure to try to handle any unsub icons that may
84 * exist for others.
85 */
86 namespace.portlet.subscribe(
87 'bugs:dupeportletloadfailed',
88 function(handlers) {
89 setup_unsubscribe_icon_handlers();
90 });
91
92 /* If loading the subscriber IDs JSON has succeeded, set up the
93 * subscription link handlers and load the subscribers from dupes.
94 */
95 namespace.portlet.subscribe(
96 'bugs:portletsubscriberidsloaded',
97 function() {
98 setup_subscription_link_handlers();
99 load_subscribers_from_duplicates();
100 });
101
102 /* If loading the subscriber IDs JSON fails we still need to load the
103 * subscribers from duplicates but we don't set up the subscription link
104 * handlers.
105 */
106 namespace.portlet.subscribe(
107 'bugs:portletsubscriberidsfailed',
108 function() {
109 load_subscribers_from_duplicates();
110 });
111
112 /*
113 * Subscribing someone else requires loading a grayed out
114 * username into the DOM until the subscribe action completes.
115 * There are a couple XHR requests in check_can_be_unsubscribed
116 * before the subscribe work can be done, so fire a custom event
117 * bugs:nameloaded and do the work here when the event fires.
118 */
119 namespace.portlet.subscribe('bugs:nameloaded', function(subscription) {
120 var error_handler = new LP.client.ErrorHandler();
121 error_handler.clearProgressUI = function() {
122 var temp_link = Y.one('#temp-username');
123 if (temp_link) {
124 var temp_parent = temp_link.get('parentNode');
125 temp_parent.removeChild(temp_link);
126 }
127 };
128 error_handler.showError = function(error_msg) {
129 Y.lp.app.errors.display_error(
130 Y.one('.menu-link-addsubscriber'), error_msg);
131 };
132
133 var config = {
134 on: {
135 success: function() {
136 var temp_link = Y.one('#temp-username');
137 var temp_spinner = Y.one('#temp-name-spinner');
138 temp_link.removeChild(temp_spinner);
139 var anim = Y.lazr.anim.green_flash({ node: temp_link });
140 anim.on('end', function() {
141 add_user_name_link(subscription);
142 var temp_parent = temp_link.get('parentNode');
143 temp_parent.removeChild(temp_link);
144 });
145 anim.run();
146 },
147 failure: error_handler.getFailureHandler()
148 },
149 parameters: {
150 person: LP.client.get_absolute_uri(
151 subscription.get('person').get('escaped_uri')),
152 suppress_notify: false
153 }
154 };
155 lp_client.named_post(bug_repr.self_link, 'subscribe', config);
156 });
157}
158
159namespace.setup_bugtask_index = function() {42namespace.setup_bugtask_index = function() {
160 setup_portlet_handlers();43 Y.lp.bugs.bugtask_index.portlets.setup_portlet_handlers();
44
161 /*45 /*
162 * Check the page for links related to overlay forms and request the HTML46 * Check the page for links related to overlay forms and request the HTML
163 * for these forms.47 * for these forms.
@@ -275,203 +159,6 @@
275159
276160
277/*161/*
278 * Initialize click handler for the subscribe someone else link.
279 *
280 * @method setup_subscribe_someone_else_handler
281 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
282 */
283function setup_subscribe_someone_else_handler(subscription) {
284 var config = {
285 header: 'Subscribe someone else',
286 step_title: 'Search',
287 picker_activator: '.menu-link-addsubscriber'
288 };
289
290 config.save = function(result) {
291 subscribe_someone_else(result, subscription);
292 };
293 var picker = Y.lp.app.picker.create('ValidPersonOrTeam', config);
294}
295
296
297/*
298 * Handle the advanced_subscription_overlay's form submissions.
299 *
300 * @method handle_advanced_subscription_overlay
301 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
302 * @param form_data {Object} The data from the submitted form.
303 */
304function handle_advanced_subscription_overlay(subscription, form_data) {
305 var link = subscription.get('link');
306 var link_parent = link.get('parentNode');
307 if (link_parent.hasClass('subscribed-false') &&
308 link_parent.hasClass('dup-subscribed-false')) {
309 // The user isn't subscribed, so subscribe them.
310 subscription.set(
311 'bug_notification_level',
312 form_data['field.bug_notification_level']);
313 subscribe_current_user(subscription);
314 } else if (
315 form_data['field.subscription'] == 'update-subscription') {
316 // The user is already subscribed and wants to update their
317 // subscription.
318 setup_client_and_bug();
319 var person_name = subscription.get('person').get('name');
320 var subscription_url =
321 lp_bug_entry.get('self_link') + '/+subscription/' +
322 person_name;
323 config = {
324 on: {
325 success: function(lp_subscription) {
326 subscription.enable_spinner('Updating subscription...');
327 lp_subscription.set(
328 'bug_notification_level',
329 form_data['field.bug_notification_level'][0])
330 save_config = {
331 on: {
332 success: function(e) {
333 subscription.disable_spinner(
334 'Edit subscription');
335 var anim = Y.lazr.anim.green_flash({
336 node: link_parent
337 });
338 anim.run();
339 },
340 failure: function(e) {
341 subscription.disable_spinner(
342 'Edit subscription');
343 var anim = Y.lazr.anim.red_flash({
344 node: link_parent
345 });
346 anim.run();
347 }
348 }
349 }
350 lp_subscription.lp_save(save_config);
351 }
352 }
353 }
354 lp_client.get(subscription_url, config);
355 } else {
356 // The user is already subscribed and wants to unsubscribe.
357 unsubscribe_current_user(subscription);
358 }
359}
360
361
362/*
363 * Create and return a FormOverlay for advanced subscription
364 * interactions.
365 *
366 * @method setup_advanced_subscription_overlay
367 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
368 */
369function setup_advanced_subscription_overlay(subscription) {
370 var subscription_overlay = new Y.lazr.FormOverlay({
371 headerContent: '<h2>Subscribe to bug</h2>',
372 form_submit_button:
373 Y.Node.create(submit_button_html),
374 form_cancel_button:
375 Y.Node.create(cancel_button_html),
376 centered: true,
377 visible: false
378 });
379 subscription_overlay.set(
380 'form_submit_callback', function(form_data) {
381 handle_advanced_subscription_overlay(subscription, form_data);
382 subscription_overlay.hide();
383 });
384
385 var subscription_link_url = subscription.get(
386 'link').get('href') + '/++form++';
387 subscription_overlay.loadFormContentAndRender(
388 subscription_link_url);
389 subscription_overlay.render('#privacy-form-container');
390 return subscription_overlay
391}
392
393
394/*
395 * Initialize callbacks for subscribe/unsubscribe links.
396 *
397 * @method setup_subscription_link_handlers
398 */
399function setup_subscription_link_handlers() {
400 if (LP.client.links.me === undefined) {
401 return;
402 }
403
404 setup_client_and_bug();
405 var subscription = new Y.lp.bugs.subscriber.Subscription({
406 link: Y.one('.menu-link-subscription'),
407 spinner: Y.one('#sub-unsub-spinner'),
408 subscriber: new Y.lp.bugs.subscriber.Subscriber({
409 uri: LP.client.links.me,
410 subscriber_ids: subscriber_ids
411 })
412 });
413
414 var is_direct = subscription.get(
415 'link').get('parentNode').hasClass('subscribed-true');
416 var has_dupes = subscription.get(
417 'link').get('parentNode').hasClass('dup-subscribed-true');
418 subscription.set('is_direct', is_direct);
419 subscription.set('has_dupes', has_dupes);
420
421 if (subscription.is_node()) {
422 subscription.get('link').on('click', function(e) {
423 e.halt();
424 subscription.set('can_be_unsubscribed', true);
425 subscription.set('person', subscription.get('subscriber'));
426 subscription.set('is_team', false);
427 var parent = e.target.get('parentNode');
428 if (namespace.use_advanced_subscriptions) {
429 var subscription_overlay =
430 setup_advanced_subscription_overlay(subscription);
431 subscription_overlay.show();
432 } else {
433 // Look for the false conditions of subscription, which
434 // is_direct_subscription, etc. don't report correctly,
435 // to make sure we only use subscribe_current_user for
436 // the current user.
437 if (parent.hasClass('subscribed-false') &&
438 parent.hasClass('dup-subscribed-false')) {
439 subscribe_current_user(subscription);
440 }
441 else {
442 unsubscribe_current_user(subscription);
443 }
444 }
445 });
446 subscription.get('link').addClass('js-action');
447 }
448
449 setup_subscribe_someone_else_handler(subscription);
450}
451
452/*
453 * Set click handlers for unsubscribe remove icons.
454 *
455 * @method setup_unsubscribe_icon_handlers
456 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
457 */
458function setup_unsubscribe_icon_handlers() {
459 var subscription = new Y.lp.bugs.subscriber.Subscription({
460 link: Y.one('.menu-link-subscription'),
461 spinner: Y.one('#sub-unsub-spinner'),
462 subscriber: new Y.lp.bugs.subscriber.Subscriber({
463 uri: LP.client.links.me,
464 subscriber_ids: subscriber_ids
465 })
466 });
467
468 Y.on('click', function(e) {
469 e.halt();
470 unsubscribe_user_via_icon(e.target, subscription);
471 }, '.unsub-icon');
472}
473
474/*
475 * Create the lp client and bug entry if we haven't done so already.162 * Create the lp client and bug entry if we haven't done so already.
476 *163 *
477 * @method setup_client_and_bug164 * @method setup_client_and_bug
@@ -850,594 +537,6 @@
850 Y.fire('lp:branch-linked', bug_branch_node);537 Y.fire('lp:branch-linked', bug_branch_node);
851}538}
852539
853/*
854 * Traverse the DOM of a given remove icon to find
855 * the user's link. Returns a URI of the form "/~username".
856 *
857 * @method get_user_uri_from_icon
858 * @param icon {Node} The node representing a remove icon.
859 * @return user_uri {String} The user's uri, without the hostname.
860 */
861function get_user_uri_from_icon(icon) {
862 var parent_div = icon.get('parentNode').get('parentNode');
863 // This should be parent_div.firstChild, but because of #text
864 // and cross-browser issues, using the YUI query syntax is
865 // safer here.
866 var user_uri = parent_div.one('a').getAttribute('href');
867
868 // Strip the domain off. We just want a path.
869 var host_start = user_uri.indexOf('//');
870 if (host_start != -1) {
871 var host_end = user_uri.indexOf('/', host_start+2);
872 return user_uri.substring(host_end, user_uri.length);
873 }
874
875 return user_uri;
876}
877
878
879/*
880 * Build the HTML for a user link for the subscribers list.
881 *
882 * @method build_user_link_html
883 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
884 * @return html {String} The HTML used for creating a subscriber link.
885 */
886function build_user_link_html(subscription) {
887 var name = subscription.get('person').get('name');
888 var css_name = subscription.get('person').get('css_name');
889 var full_name = subscription.get('person').get('full_display_name');
890 // Be paranoid about display_name, since timeouts or other errors
891 // could mean display_name wasn't set on initialization.
892 if (subscription.get('person').get('display_name') === '') {
893 subscription.get('person').set_display_name();
894 }
895 var display_name = subscription.get('person').get('display_name');
896 var terms = {
897 name: name,
898 css_name: css_name,
899 display_name: display_name,
900 full_name: full_name
901 };
902
903 if (subscription.is_current_user_subscribing()) {
904 terms.subscribed_by = 'themselves';
905 } else {
906 terms.subscribed_by = 'by ' + full_name;
907 }
908
909 var html = Y.Node.create('<div><a></a></div>');
910 html.addClass(terms.css_name);
911
912 if (subscription.is_direct_subscription()) {
913 html.set('id', 'direct-' + terms.css_name);
914 } else {
915 html.set('id', 'dupe-' + terms.css_name);
916 }
917
918 html.one('a')
919 .set('href', '/~' + terms.name)
920 .set('name', terms.full_name)
921 .set('title', 'Subscribed ' + terms.subscribed_by);
922
923 var span;
924 if (subscription.is_team()) {
925 span = '<span class="sprite team"></span>';
926 } else {
927 span = '<span class="sprite person"></span>';
928 }
929
930 html.one('a')
931 .appendChild(Y.Node.create(span))
932 .appendChild(document.createTextNode(terms.display_name));
933
934 // Add remove icon if the current user can unsubscribe the subscriber.
935 if (subscription.can_be_unsubscribed_by_user()) {
936 var icon_html = Y.Node.create(
937 '<a href="+subscribe">' +
938 '<img class="unsub-icon" src="/@@/remove" alt="Remove" /></a>');
939 icon_html
940 .set('id', 'unsubscribe-' + terms.css_name)
941 .set('title', 'Unsubscribe ' + terms.full_name);
942 icon_html.one('img')
943 .set('id', 'unsubscribe-icon-' + terms.css_name);
944 html.appendChild(icon_html);
945 }
946
947 return html;
948}
949
950/*
951 * Used to remove the user's name from the subscriber's list.
952 *
953 * @method remove_user_name_link
954 * @param user_node {Node} Node representing the user name link.
955 */
956function remove_user_name_link(user_node) {
957 var parent = user_node.get('parentNode');
958 parent.removeChild(user_node);
959}
960
961/*
962 * Returns the next node in alphabetical order after the subscriber
963 * node now being added. No node is returned to append to end of list.
964 *
965 * The name can appear in one of two different lists. 1) The list of
966 * subscribers that can be unsubscribed by the current user, and
967 * 2) the list of subscribers that cannont be unsubscribed.
968 *
969 * @method get_next_subscriber_node
970 * @param subscription_link {Node} The sub/unsub link.
971 * @return {Node} The node appearing next in the subscriber list or
972 * undefined if no node is next.
973 */
974function get_next_subscriber_node(subscription) {
975 var full_name = subscription.get('person').get('full_display_name');
976 var can_be_unsubscribed = subscription.can_be_unsubscribed_by_user();
977 var nodes_by_name = {};
978 var unsubscribables = [];
979 var not_unsubscribables = [];
980
981 // Use the list of subscribers pulled from the DOM to have sortable
982 // lists of unsubscribable vs. not unsubscribale person links.
983 var all_subscribers = Y.all('#subscribers-links div');
984 if (all_subscribers.size() > 0) {
985 all_subscribers.each(function(sub_link) {
986 if (sub_link.getAttribute('id') != 'temp-username') {
987 // User's displayname is found via the link's "name"
988 // attribute.
989 var sub_link_name = sub_link.one('a').getAttribute('name');
990 nodes_by_name[sub_link_name] = sub_link;
991 if (sub_link.one('img.unsub-icon')) {
992 unsubscribables.push(sub_link_name);
993 } else {
994 not_unsubscribables.push(sub_link_name);
995 }
996 }
997 });
998
999 // Add the current subscription.
1000 if (can_be_unsubscribed) {
1001 unsubscribables.push(full_name);
1002 } else {
1003 not_unsubscribables.push(full_name);
1004 }
1005 unsubscribables.sort();
1006 not_unsubscribables.sort();
1007 } else {
1008 // If there is no all_subscribers, then we're dealing with
1009 // the printed None, so return.
1010 return;
1011 }
1012
1013 var i;
1014 if ((!unsubscribables && !not_unsubscribables) ||
1015 // If A) neither list exists, B) the user belongs in the second
1016 // list but the second list doesn't exist, or C) user belongs in the
1017 // first list and the second doesn't exist, return no node to append.
1018 (!can_be_unsubscribed && !not_unsubscribables) ||
1019 (can_be_unsubscribed && unsubscribables && !not_unsubscribables)) {
1020 return;
1021 } else if (
1022 // If the user belongs in the first list, and the first list
1023 // doesn't exist, but the second one does, return the first node
1024 // in the second list.
1025 can_be_unsubscribed && !unsubscribables && not_unsubscribables) {
1026 return nodes_by_name[not_unsubscribables[0]];
1027 } else if (can_be_unsubscribed) {
1028 // If the user belongs in the first list, loop the list for position.
1029 for (i=0; i<unsubscribables.length; i++) {
1030 if (unsubscribables[i] == full_name) {
1031 if (i+1 < unsubscribables.length) {
1032 return nodes_by_name[unsubscribables[i+1]];
1033 // If the current link should go at the end of the first
1034 // list and we're at the end of that list, return the
1035 // first node of the second list. Due to earlier checks
1036 // we can be sure this list exists.
1037 } else if (i+1 >= unsubscribables.length) {
1038 return nodes_by_name[not_unsubscribables[0]];
1039 }
1040 }
1041 }
1042 } else if (!can_be_unsubscribed) {
1043 // If user belongs in the second list, loop the list for position.
1044 for (i=0; i<not_unsubscribables.length; i++) {
1045 if (not_unsubscribables[i] == full_name) {
1046 if (i+1 < not_unsubscribables.length) {
1047 return nodes_by_name[not_unsubscribables[i+1]];
1048 } else {
1049 return;
1050 }
1051 }
1052 }
1053 }
1054}
1055
1056/*
1057 * Add the user name to the subscriber's list.
1058 *
1059 * @method add_user_name_link
1060 */
1061function add_user_name_link(subscription) {
1062 var person = subscription.get('person');
1063 var link_node = build_user_link_html(subscription);
1064 var subscribers = Y.one('#subscribers-links');
1065 if (subscription.is_current_user_subscribing()) {
1066 // If this is the current user, then top post the name and be done.
1067 subscribers.insertBefore(link_node, subscribers.get('firstChild'));
1068 } else {
1069 var next = get_next_subscriber_node(subscription);
1070 if (next) {
1071 subscribers.insertBefore(link_node, next);
1072 } else {
1073 // Handle the case of the displayed "None".
1074 var none_subscribers = Y.one('#none-subscribers');
1075 if (none_subscribers) {
1076 var none_parent = none_subscribers.get('parentNode');
1077 none_parent.removeChild(none_subscribers);
1078 }
1079 subscribers.appendChild(link_node);
1080 }
1081 }
1082
1083 // Set the click handler if adding a remove icon.
1084 if (subscription.can_be_unsubscribed_by_user()) {
1085 var remove_icon =
1086 Y.one('#unsubscribe-icon-' + person.get('css_name'));
1087 remove_icon.on('click', function(e) {
1088 e.halt();
1089 unsubscribe_user_via_icon(e.target, subscription);
1090 });
1091 }
1092}
1093
1094/*
1095 * Add a grayed out, temporary user name when subscribing
1096 * someone else.
1097 *
1098 * @method add_temp_user_name
1099 * @param subscription_link {Node} The sub/unsub link.
1100 */
1101function add_temp_user_name(subscription) {
1102 // Be paranoid about display_name, since timeouts or other errors
1103 // could mean display_name wasn't set on initialization.
1104 if (subscription.get('person').get('display_name') === '') {
1105 subscription.get('person').set_display_name();
1106 }
1107 var display_name = subscription.get('person').get('display_name');
1108 var img_src;
1109 if (subscription.is_team()) {
1110 img_src = '/@@/teamgray';
1111 } else {
1112 img_src = '/@@/persongray';
1113 }
1114
1115 // The <span>...</span> below must *not* be <span/>. On FF (maybe
1116 // others, but at least on FF 3.0.11) will then not notice any
1117 // following sibling nodes, like the spinner image.
1118 var link_node = Y.Node.create([
1119 '<div id="temp-username"> ',
1120 ' <img alt="" width="14" height="14" />',
1121 ' <span>Other Display Name</span>',
1122 ' <img id="temp-name-spinner" src="/@@/spinner" alt="" ',
1123 ' style="position:absolute;right:8px" /></div>'].join(''));
1124 link_node.one('img').set('src', img_src);
1125 link_node.replaceChild(
1126 document.createTextNode(display_name),
1127 link_node.one('span'));
1128
1129 var subscribers = Y.one('#subscribers-links');
1130 var next = get_next_subscriber_node(subscription);
1131 if (next) {
1132 subscribers.insertBefore(link_node, next);
1133 } else {
1134 // Handle the case of the displayed "None".
1135 var none_subscribers = Y.one('#none-subscribers');
1136 if (none_subscribers) {
1137 var none_parent = none_subscribers.get('parentNode');
1138 none_parent.removeChild(none_subscribers);
1139 }
1140 subscribers.appendChild(link_node);
1141 }
1142
1143 // Fire a custom event to know it's safe to begin
1144 // any actual subscribing work.
1145 namespace.portlet.fire('bugs:nameloaded', subscription);
1146}
1147
1148/*
1149 * Add the "None" div to the subscribers list if
1150 * there aren't any subscribers left.
1151 *
1152 * @method set_none_for_empty_subscribers
1153 */
1154function set_none_for_empty_subscribers() {
1155 var subscriber_list = Y.one('#subscribers-links');
1156 // Assume if subscriber_list has no child divs
1157 // then the list of subscribers is empty.
1158 if (!Y.Lang.isValue(subscriber_list.one('div')) &&
1159 !Y.Lang.isValue(Y.one('#none-subscribers'))) {
1160 var none_div = Y.Node.create('<div id="none-subscribers">None</div>');
1161 subscriber_list.appendChild(none_div);
1162 }
1163
1164 // Clear the empty duplicate subscribers list if it exists.
1165 var dup_list = Y.one('#subscribers-from-duplicates');
1166 if (Y.Lang.isValue(dup_list) &&
1167 !Y.Lang.isValue(dup_list.one('div'))) {
1168 var parent = dup_list.get('parentNode');
1169 parent.removeChild(dup_list);
1170 }
1171}
1172
1173/*
1174 * Set the class on subscription link's parentNode.
1175 *
1176 * This is used to reset the class used by the
1177 * click handler to know which link was clicked.
1178 *
1179 * @method set_subscription_link_parent_class
1180 * @param subscription_link {Node} The sub/unsub link.
1181 * @param subscribed {Boolean} The sub/unsub'ed flag for the class.
1182 * @param dupe_subscribed {Boolean} The sub/unsub'ed flag for dupes
1183 * on the class.
1184 */
1185function set_subscription_link_parent_class(
1186 user_link, subscribed, dupe_subscribed) {
1187
1188 var parent = user_link.get('parentNode');
1189 if (subscribed) {
1190 parent.removeClass('subscribed-false');
1191 parent.addClass('subscribed-true');
1192 } else {
1193 parent.removeClass('subscribed-true');
1194 parent.addClass('subscribed-false');
1195 }
1196
1197 if (dupe_subscribed) {
1198 parent.removeClass('dup-subscribed-false');
1199 parent.addClass('dup-subscribed-true');
1200 } else {
1201 parent.removeClass('dup-subscribed-true');
1202 parent.addClass('dup-subscribed-false');
1203 }
1204}
1205
1206/*
1207 * Unsubscribe a user from this bugtask when a remove icon is clicked.
1208 *
1209 * @method unsubscribe_user_via_icon
1210 * @param icon {Node} The remove icon that was clicked.
1211 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
1212*/
1213function unsubscribe_user_via_icon(icon, subscription) {
1214 icon.set('src', '/@@/spinner');
1215 var icon_parent = icon.get('parentNode');
1216
1217 var user_uri = get_user_uri_from_icon(icon);
1218 var person = new Y.lp.bugs.subscriber.Subscriber({
1219 uri: user_uri,
1220 subscriber_ids: subscriber_ids
1221 });
1222 subscription.set('person', person);
1223
1224 // Determine if this is a dupe.
1225 var is_dupe;
1226 var icon_parent_div = icon_parent.get('parentNode');
1227 var dupe_id = 'dupe-' + person.get('css_name');
1228 if (icon_parent_div.get('id') == dupe_id) {
1229 is_dupe = true;
1230 } else {
1231 is_dupe = false;
1232 }
1233
1234 var error_handler = new LP.client.ErrorHandler();
1235 error_handler.clearProgressUI = function () {
1236 icon.set('src', '/@@/remove');
1237 // Grab the icon again to reset to click handler.
1238 var unsubscribe_icon = Y.one(
1239 '#unsubscribe-icon-' + person.get('css_name'));
1240 unsubscribe_icon.on('click', function(e) {
1241 e.halt();
1242 unsubscribe_user_via_icon(e.target, subscription);
1243 });
1244
1245 };
1246 error_handler.showError = function (error_msg) {
1247 var flash_node = Y.one('.' + person.get('css_name'));
1248 Y.lp.app.errors.display_error(flash_node, error_msg);
1249
1250 };
1251
1252 var subscription_link = subscription.get('link');
1253 var config = {
1254 on: {
1255 success: function(client) {
1256 icon_parent.removeChild(icon);
1257 var anim = Y.lazr.anim.green_flash({ node: icon_parent_div });
1258 anim.on('end', function(e) {
1259 remove_user_name_link(icon_parent_div);
1260 set_none_for_empty_subscribers();
1261 var person_link = Y.one('.' + person.get('css_name'));
1262 if (Y.Lang.isNull(person_link) &&
1263 subscription.is_current_user_subscribing()) {
1264 // Current user has been completely unsubscribed.
1265 subscription.disable_spinner(
1266 subscription_labels.SUBSCRIBE);
1267 set_subscription_link_parent_class(
1268 subscription_link, false, false);
1269 subscription.set('is_direct', false);
1270 subscription.set('has_dupes', false);
1271 } else {
1272 if (is_dupe) {
1273 // A direct subscription remains.
1274 set_subscription_link_parent_class(
1275 subscription_link, true, false);
1276 subscription.set('is_direct', true);
1277 subscription.set('has_dupes', false);
1278 } else {
1279 // A dupe subscription remains.
1280 set_subscription_link_parent_class(
1281 subscription_link, false, true);
1282 subscription.set('is_direct', false);
1283 subscription.set('has_dupes', true);
1284 }
1285 }
1286 });
1287 anim.run();
1288 },
1289
1290 failure: error_handler.getFailureHandler()
1291 }
1292 };
1293
1294 if (!subscription.is_current_user_subscribing()) {
1295 config.parameters = {
1296 person: LP.client.get_absolute_uri(user_uri)
1297 };
1298 }
1299
1300 if (is_dupe) {
1301 lp_client.named_post(
1302 bug_repr.self_link, 'unsubscribeFromDupes', config);
1303 } else {
1304 lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
1305 }
1306}
1307
1308/*
1309 * Subscribe the current user via the LP API.
1310 *
1311 * @method subscribe_current_user
1312 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
1313 */
1314function subscribe_current_user(subscription) {
1315 subscription.enable_spinner('Subscribing...');
1316 var subscription_link = subscription.get('link');
1317 var subscriber = subscription.get('subscriber');
1318 var bug_notification_level = subscription.get('bug_notification_level');
1319
1320 var error_handler = new LP.client.ErrorHandler();
1321 error_handler.clearProgressUI = function () {
1322 subscription.disable_spinner();
1323 };
1324 error_handler.showError = function (error_msg) {
1325 Y.lp.app.errors.display_error(subscription_link, error_msg);
1326 };
1327
1328 var config = {
1329 on: {
1330 success: function(client) {
1331 if (namespace.use_advanced_subscriptions) {
1332 subscription.disable_spinner(
1333 subscription_labels.EDIT);
1334 } else {
1335 subscription.disable_spinner(
1336 subscription_labels.UNSUBSCRIBE);
1337 }
1338
1339 if (subscription.has_duplicate_subscriptions()) {
1340 set_subscription_link_parent_class(
1341 subscription_link, true, true);
1342 } else {
1343 set_subscription_link_parent_class(
1344 subscription_link, true, false);
1345 }
1346
1347 // Handle the case where the subscriber's list displays
1348 // "None".
1349 var empty_subscribers = Y.one("#none-subscribers");
1350 if (empty_subscribers) {
1351 var parent = empty_subscribers.get('parentNode');
1352 parent.removeChild(empty_subscribers);
1353 }
1354
1355 add_user_name_link(subscription);
1356
1357 var flash_node = Y.one('.' + subscriber.get('css_name'));
1358 var anim = Y.lazr.anim.green_flash({ node: flash_node });
1359 anim.run();
1360 },
1361
1362 failure: error_handler.getFailureHandler()
1363 },
1364
1365 parameters: {
1366 person: LP.client.get_absolute_uri(subscriber.get('escaped_uri')),
1367 suppress_notify: false,
1368 level: bug_notification_level
1369 }
1370 };
1371 lp_client.named_post(bug_repr.self_link, 'subscribe', config);
1372}
1373
1374/*
1375 * Unsubscribe the current user via the LP API.
1376 *
1377 * @method unsubscribe_current_user
1378 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
1379 */
1380function unsubscribe_current_user(subscription) {
1381 subscription.enable_spinner('Unsubscribing...');
1382 var subscription_link = subscription.get('link');
1383 var subscriber = subscription.get('subscriber');
1384
1385 var error_handler = new LP.client.ErrorHandler();
1386 error_handler.clearProgressUI = function () {
1387 subscription.disable_spinner();
1388 };
1389 error_handler.showError = function (error_msg) {
1390 Y.lp.app.errors.display_error(subscription_link, error_msg);
1391 };
1392
1393 var config = {
1394 on: {
1395 success: function(client) {
1396 if (subscription.is_direct_subscription() &&
1397 subscription.has_duplicate_subscriptions()) {
1398 // Don't change the 'Unsubscribe' text if
1399 // dupe subscriptions remain.
1400 subscription.disable_spinner();
1401 set_subscription_link_parent_class(
1402 subscription_link, false, true);
1403 subscription.set('is_direct', false);
1404 } else if (subscription.is_direct_subscription() &&
1405 !subscription.has_duplicate_subscriptions()) {
1406 // Only unsub'ing a direct subscriber here.
1407 subscription.disable_spinner(
1408 subscription_labels.SUBSCRIBE);
1409 set_subscription_link_parent_class(
1410 subscription_link, false, false);
1411 subscription.set('is_direct', false);
1412 } else {
1413 // Only unsub'ing dupes here.
1414 subscription.disable_spinner(
1415 subscription_labels.SUBSCRIBE);
1416 set_subscription_link_parent_class(
1417 subscription_link, false, false);
1418 subscription.set('has_dupes', false);
1419 }
1420
1421 var flash_node = Y.one('.' + subscriber.get('css_name'));
1422 var anim = Y.lazr.anim.green_flash({ node: flash_node });
1423 anim.on('end', function(e) {
1424 remove_user_name_link(flash_node);
1425 set_none_for_empty_subscribers();
1426 });
1427 anim.run();
1428 },
1429
1430 failure: error_handler.getFailureHandler()
1431 }
1432 };
1433 if (subscription.is_direct_subscription()) {
1434 lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
1435 } else {
1436 lp_client.named_post(
1437 bug_repr.self_link, 'unsubscribeFromDupes', config);
1438 }
1439}
1440
1441540
1442/**541/**
1443 * Set up a bug task table row.542 * Set up a bug task table row.
@@ -1815,106 +914,6 @@
1815 LP.client.cache.bug.self_link, 'markUserAffected', config);914 LP.client.cache.bug.self_link, 'markUserAffected', config);
1816 }915 }
1817});916});
1818
1819/*
1820 * Check if the current user can unsubscribe the person
1821 * being subscribed.
1822 *
1823 * This must be done in JavaScript, since the subscription
1824 * hasn't completed yet, and so, can_be_unsubscribed_by_user
1825 * cannot be used.
1826 *
1827 * @method check_can_be_unsubscribed
1828 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
1829 */
1830function check_can_be_unsubscribed(subscription) {
1831 var error_handler = new LP.client.ErrorHandler();
1832 error_handler.showError = function (error_msg) {
1833 Y.lp.app.errors.display_error(
1834 Y.one('.menu-link-addsubscriber'), error_msg);
1835 };
1836
1837 var config = {
1838 on: {
1839 success: function(result) {
1840 var is_team = result.get('is_team');
1841 subscription.set('is_team', is_team);
1842 var final_config = {
1843 on: {
1844 success: function(result) {
1845 var team_member = false;
1846 for (var i=0; i<result.entries.length; i++) {
1847 if (result.entries[i].member_link ==
1848 LP.client.get_absolute_uri(
1849 subscription.get(
1850 'subscriber').get('uri'))) {
1851 team_member = true;
1852 }
1853 }
1854
1855 if (team_member) {
1856 subscription.set('can_be_unsubscribed', true);
1857 add_temp_user_name(subscription);
1858 } else {
1859 subscription.set(
1860 'can_be_unsubscribed', false);
1861 add_temp_user_name(subscription);
1862 }
1863 },
1864
1865 failure: error_handler.getFailureHandler()
1866 }
1867 };
1868
1869 if (is_team) {
1870 // Get a list of members to see if current user
1871 // is a team member.
1872 var members = result.get(
1873 'members_details_collection_link');
1874 lp_client.get(members, final_config);
1875 } else {
1876 subscription.set('can_be_unsubscribed', false);
1877 add_temp_user_name(subscription);
1878 }
1879 },
1880
1881 failure: error_handler.getFailureHandler()
1882 }
1883 };
1884 lp_client.get(LP.client.get_absolute_uri(
1885 subscription.get('person').get('escaped_uri')), config);
1886}
1887
1888/*
1889 * Subscribe a person or team other than the current user.
1890 * This is a callback for the subscribe someone else picker.
1891 *
1892 * @method subscribe_someone_else
1893 * @result {Object} The object representing a person returned by the API.
1894 */
1895function subscribe_someone_else(result, subscription) {
1896 var person = new Y.lp.bugs.subscriber.Subscriber({
1897 uri: result.api_uri,
1898 display_name: result.title,
1899 subscriber_ids: subscriber_ids
1900 });
1901 subscription.set('person', person);
1902
1903 var error_handler = new LP.client.ErrorHandler();
1904 error_handler.showError = function(error_msg) {
1905 Y.lp.app.errors.display_error(
1906 Y.one('.menu-link-addsubscriber'), error_msg);
1907 };
1908
1909 if (subscription.is_already_subscribed()) {
1910 error_handler.showError(
1911 subscription.get('person').get('full_display_name') +
1912 ' has already been subscribed');
1913 } else {
1914 check_can_be_unsubscribed(subscription);
1915 }
1916}
1917
1918/*917/*
1919 * Click handling to pass comment text to the attachment918 * Click handling to pass comment text to the attachment
1920 * page if there is a comment.919 * page if there is a comment.
@@ -1935,114 +934,10 @@
1935 });934 });
1936}935}
1937936
1938function load_subscribers_from_duplicates() {
1939 if (Y.UA.ie) {
1940 return null;
1941 }
1942
1943 Y.one('#subscribers-portlet-dupe-spinner').setStyle(
1944 'display', 'block');
1945
1946 function hide_spinner() {
1947 Y.one('#subscribers-portlet-dupe-spinner').setStyle(
1948 'display', 'none');
1949 // Fire a custom event to signal failure, so that
1950 // any remaining unsub icons can be hooked up.
1951 namespace.portlet.fire('bugs:dupeportletloadfailed');
1952 }
1953
1954 function on_success(transactionid, response, args) {
1955 hide_spinner();
1956
1957 var dupe_subscribers_container = Y.one(
1958 '#subscribers-from-duplicates-container');
1959 dupe_subscribers_container.set(
1960 'innerHTML',
1961 dupe_subscribers_container.get('innerHTML') +
1962 response.responseText);
1963
1964 // Fire a custom portlet loaded event to notify when
1965 // it's safe to setup dupe subscriber link callbacks.
1966 namespace.portlet.fire('bugs:dupeportletloaded');
1967 }
1968
1969 var config = {on: {success: on_success,
1970 failure: hide_spinner}};
1971 var url = Y.one(
1972 '#subscribers-from-dupes-content-link').getAttribute(
1973 'href').replace('bugs.', '');
1974 Y.io(url, config);
1975}
1976
1977namespace.load_subscribers_portlet = function(
1978 subscription_link, subscription_link_handler) {
1979 if (Y.UA.ie) {
1980 return null;
1981 }
1982
1983 Y.one('#subscribers-portlet-spinner').setStyle('display', 'block');
1984
1985 function hide_spinner() {
1986 Y.one('#subscribers-portlet-spinner').setStyle('display', 'none');
1987 // Fire a custom event to notify that the initial click
1988 // handler on subscription_link set above should be
1989 // cleared.
1990 if (namespace) {
1991 namespace.portlet.fire(
1992 'bugs:portletloadfailed', subscription_link_handler);
1993 }
1994 }
1995
1996 function setup_portlet(transactionid, response, args) {
1997 hide_spinner();
1998 var portlet = Y.one('#portlet-subscribers');
1999 portlet.set('innerHTML',
2000 portlet.get('innerHTML') + response.responseText);
2001
2002 // Fire a custom portlet loaded event to notify when
2003 // it's safe to setup subscriber link callbacks.
2004 namespace.portlet.fire('bugs:portletloaded');
2005 }
2006
2007 var config = {on: {success: setup_portlet,
2008 failure: hide_spinner}};
2009 var url = Y.one(
2010 '#subscribers-content-link').getAttribute('href').replace(
2011 'bugs.', '');
2012 Y.io(url, config);
2013};
2014
2015function load_subscriber_ids() {
2016 function on_success(transactionid, response, args) {
2017 try {
2018 subscriber_ids = Y.JSON.parse(response.responseText);
2019
2020 // Fire a custom event to trigger the setting-up of the
2021 // subscription handlers.
2022 namespace.portlet.fire('bugs:portletsubscriberidsloaded');
2023 } catch (e) {
2024 // Fire an event to signal failure. This ensures that the
2025 // subscribers-from-dupes still get loaded into the portlet.
2026 namespace.portlet.fire('bugs:portletsubscriberidsfailed');
2027 }
2028 }
2029
2030 function on_failure() {
2031 // Fire an event to signal failure. This ensures that the
2032 // subscribers-from-dupes still get loaded into the portlet.
2033 namespace.portlet.fire('bugs:portletsubscriberidsfailed');
2034 }
2035
2036 var config = {on: {success: on_success,
2037 failure: on_failure}};
2038 var url = Y.one(
2039 '#subscribers-ids-link').getAttribute('href');
2040 Y.io(url, config);
2041}
2042937
2043}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base",938}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base",
2044 "json-parse", "substitute", "widget-position-ext",939 "json-parse", "substitute", "widget-position-ext",
2045 "lazr.formoverlay", "lazr.anim", "lazr.base",940 "lazr.formoverlay", "lazr.anim", "lazr.base",
2046 "lazr.overlay", "lazr.choiceedit", "lp.app.picker",941 "lazr.overlay", "lazr.choiceedit", "lp.app.picker",
2047 "lp.client.plugins", "lp.bugs.subscriber",942 "lp.client.plugins", "lp.bugs.bugtask_index.portlets",
2048 "lp.app.errors"]});943 "lp.bugs.subscriber", "lp.app.errors"]});
2049944
=== added file 'lib/lp/bugs/javascript/bugtask_index_portlets.js'
--- lib/lp/bugs/javascript/bugtask_index_portlets.js 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/javascript/bugtask_index_portlets.js 2011-02-15 18:19:47 +0000
@@ -0,0 +1,1152 @@
1/* Copyright 2011 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *
4 * Form overlay widgets and subscriber handling for bug pages.
5 *
6 * @module bugs
7 * @submodule bugtask_index.portlets
8 */
9
10YUI.add('lp.bugs.bugtask_index.portlets', function(Y) {
11
12var namespace = Y.namespace('lp.bugs.bugtask_index.portlets');
13
14// The launchpad js client used.
15var lp_client;
16
17// The launchpad client entry for the current bug.
18var lp_bug_entry;
19
20// The bug itself, taken from cache.
21var bug_repr;
22
23// A boolean telling us whether advanced subscription features are to be
24// used or not.
25var use_advanced_subscriptions = false;
26
27var subscription_labels = Y.lp.bugs.subscriber.subscription_labels;
28
29submit_button_html =
30 '<button type="submit" name="field.actions.change" ' +
31 'value="Change" class="lazr-pos lazr-btn" >OK</button>';
32cancel_button_html =
33 '<button type="button" name="field.actions.cancel" ' +
34 'class="lazr-neg lazr-btn" >Cancel</button>';
35
36// The set of subscriber CSS IDs as a JSON struct.
37var subscriber_ids;
38
39/*
40 * An object representing the bugtask subscribers portlet.
41 *
42 * Since the portlet loads via XHR and inline subscribing
43 * depends on that portlet being loaded, setup a custom
44 * event object, to provide a hook for initializing subscription
45 * link callbacks after custom events.
46 */
47var PortletTarget = function() {};
48Y.augment(PortletTarget, Y.Event.Target);
49namespace.portlet = new PortletTarget();
50
51/*
52 * Create the lp client and bug entry if we haven't done so already.
53 *
54 * @method setup_client_and_bug
55 */
56function setup_client_and_bug() {
57 lp_client = new LP.client.Launchpad();
58
59 if (bug_repr === undefined) {
60 bug_repr = LP.client.cache.bug;
61 lp_bug_entry = new LP.client.Entry(
62 lp_client, bug_repr, bug_repr.self_link);
63 }
64}
65
66namespace.load_subscribers_portlet = function(
67 subscription_link, subscription_link_handler) {
68 if (Y.UA.ie) {
69 return null;
70 }
71
72 Y.one('#subscribers-portlet-spinner').setStyle('display', 'block');
73
74 function hide_spinner() {
75 Y.one('#subscribers-portlet-spinner').setStyle('display', 'none');
76 // Fire a custom event to notify that the initial click
77 // handler on subscription_link set above should be
78 // cleared.
79 if (namespace) {
80 namespace.portlet.fire(
81 'bugs:portletloadfailed', subscription_link_handler);
82 }
83 }
84
85 function setup_portlet(transactionid, response, args) {
86 hide_spinner();
87 var portlet = Y.one('#portlet-subscribers');
88 portlet.set('innerHTML',
89 portlet.get('innerHTML') + response.responseText);
90
91 // Fire a custom portlet loaded event to notify when
92 // it's safe to setup subscriber link callbacks.
93 namespace.portlet.fire('bugs:portletloaded');
94 }
95
96 var config = {on: {success: setup_portlet,
97 failure: hide_spinner}};
98 var url = Y.one(
99 '#subscribers-content-link').getAttribute('href').replace(
100 'bugs.', '');
101 Y.io(url, config);
102};
103
104
105namespace.setup_portlet_handlers = function() {
106 namespace.portlet.subscribe('bugs:portletloaded', function() {
107 load_subscriber_ids();
108 });
109 namespace.portlet.subscribe('bugs:dupeportletloaded', function() {
110 setup_unsubscribe_icon_handlers();
111 });
112 /*
113 * If the subscribers portlet fails to load, clear any
114 * click handlers, so the normal subscribe page can be reached.
115 */
116 namespace.portlet.subscribe('bugs:portletloadfailed', function(handlers) {
117 if (Y.Lang.isArray(handlers)) {
118 var click_handler = handlers[0];
119 click_handler.detach();
120 }
121 });
122 /* If the dupe subscribers portlet fails to load,
123 * be sure to try to handle any unsub icons that may
124 * exist for others.
125 */
126 namespace.portlet.subscribe(
127 'bugs:dupeportletloadfailed',
128 function(handlers) {
129 setup_unsubscribe_icon_handlers();
130 });
131
132 /* If loading the subscriber IDs JSON has succeeded, set up the
133 * subscription link handlers and load the subscribers from dupes.
134 */
135 namespace.portlet.subscribe(
136 'bugs:portletsubscriberidsloaded',
137 function() {
138 setup_subscription_link_handlers();
139 load_subscribers_from_duplicates();
140 });
141
142 /* If loading the subscriber IDs JSON fails we still need to load the
143 * subscribers from duplicates but we don't set up the subscription link
144 * handlers.
145 */
146 namespace.portlet.subscribe(
147 'bugs:portletsubscriberidsfailed',
148 function() {
149 load_subscribers_from_duplicates();
150 });
151
152 /*
153 * Subscribing someone else requires loading a grayed out
154 * username into the DOM until the subscribe action completes.
155 * There are a couple XHR requests in check_can_be_unsubscribed
156 * before the subscribe work can be done, so fire a custom event
157 * bugs:nameloaded and do the work here when the event fires.
158 */
159 namespace.portlet.subscribe('bugs:nameloaded', function(subscription) {
160 var error_handler = new LP.client.ErrorHandler();
161 error_handler.clearProgressUI = function() {
162 var temp_link = Y.one('#temp-username');
163 if (temp_link) {
164 var temp_parent = temp_link.get('parentNode');
165 temp_parent.removeChild(temp_link);
166 }
167 };
168 error_handler.showError = function(error_msg) {
169 Y.lp.app.errors.display_error(
170 Y.one('.menu-link-addsubscriber'), error_msg);
171 };
172
173 var config = {
174 on: {
175 success: function() {
176 var temp_link = Y.one('#temp-username');
177 var temp_spinner = Y.one('#temp-name-spinner');
178 temp_link.removeChild(temp_spinner);
179 var anim = Y.lazr.anim.green_flash({ node: temp_link });
180 anim.on('end', function() {
181 add_user_name_link(subscription);
182 var temp_parent = temp_link.get('parentNode');
183 temp_parent.removeChild(temp_link);
184 });
185 anim.run();
186 },
187 failure: error_handler.getFailureHandler()
188 },
189 parameters: {
190 person: LP.client.get_absolute_uri(
191 subscription.get('person').get('escaped_uri')),
192 suppress_notify: false
193 }
194 };
195 lp_client.named_post(bug_repr.self_link, 'subscribe', config);
196 });
197}
198
199function load_subscriber_ids() {
200 function on_success(transactionid, response, args) {
201 try {
202 subscriber_ids = Y.JSON.parse(response.responseText);
203
204 // Fire a custom event to trigger the setting-up of the
205 // subscription handlers.
206 namespace.portlet.fire('bugs:portletsubscriberidsloaded');
207 } catch (e) {
208 // Fire an event to signal failure. This ensures that the
209 // subscribers-from-dupes still get loaded into the portlet.
210 namespace.portlet.fire('bugs:portletsubscriberidsfailed');
211 }
212 }
213
214 function on_failure() {
215 // Fire an event to signal failure. This ensures that the
216 // subscribers-from-dupes still get loaded into the portlet.
217 namespace.portlet.fire('bugs:portletsubscriberidsfailed');
218 }
219
220 var config = {on: {success: on_success,
221 failure: on_failure}};
222 var url = Y.one(
223 '#subscribers-ids-link').getAttribute('href');
224 Y.io(url, config);
225}
226
227/*
228 * Set click handlers for unsubscribe remove icons.
229 *
230 * @method setup_unsubscribe_icon_handlers
231 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
232 */
233function setup_unsubscribe_icon_handlers() {
234 var subscription = new Y.lp.bugs.subscriber.Subscription({
235 link: Y.one('.menu-link-subscription'),
236 spinner: Y.one('#sub-unsub-spinner'),
237 subscriber: new Y.lp.bugs.subscriber.Subscriber({
238 uri: LP.client.links.me,
239 subscriber_ids: subscriber_ids
240 })
241 });
242
243 Y.on('click', function(e) {
244 e.halt();
245 unsubscribe_user_via_icon(e.target, subscription);
246 }, '.unsub-icon');
247}
248
249/*
250 * Initialize callbacks for subscribe/unsubscribe links.
251 *
252 * @method setup_subscription_link_handlers
253 */
254function setup_subscription_link_handlers() {
255 if (LP.client.links.me === undefined) {
256 return;
257 }
258
259 setup_client_and_bug();
260 var subscription = new Y.lp.bugs.subscriber.Subscription({
261 link: Y.one('.menu-link-subscription'),
262 spinner: Y.one('#sub-unsub-spinner'),
263 subscriber: new Y.lp.bugs.subscriber.Subscriber({
264 uri: LP.client.links.me,
265 subscriber_ids: subscriber_ids
266 })
267 });
268
269 var is_direct = subscription.get(
270 'link').get('parentNode').hasClass('subscribed-true');
271 var has_dupes = subscription.get(
272 'link').get('parentNode').hasClass('dup-subscribed-true');
273 subscription.set('is_direct', is_direct);
274 subscription.set('has_dupes', has_dupes);
275
276 if (subscription.is_node()) {
277 subscription.get('link').on('click', function(e) {
278 e.halt();
279 subscription.set('can_be_unsubscribed', true);
280 subscription.set('person', subscription.get('subscriber'));
281 subscription.set('is_team', false);
282 var parent = e.target.get('parentNode');
283 if (namespace.use_advanced_subscriptions) {
284 var subscription_overlay =
285 setup_advanced_subscription_overlay(subscription);
286 subscription_overlay.show();
287 } else {
288 // Look for the false conditions of subscription, which
289 // is_direct_subscription, etc. don't report correctly,
290 // to make sure we only use subscribe_current_user for
291 // the current user.
292 if (parent.hasClass('subscribed-false') &&
293 parent.hasClass('dup-subscribed-false')) {
294 subscribe_current_user(subscription);
295 }
296 else {
297 unsubscribe_current_user(subscription);
298 }
299 }
300 });
301 subscription.get('link').addClass('js-action');
302 }
303
304 setup_subscribe_someone_else_handler(subscription);
305}
306
307function load_subscribers_from_duplicates() {
308 if (Y.UA.ie) {
309 return null;
310 }
311
312 Y.one('#subscribers-portlet-dupe-spinner').setStyle(
313 'display', 'block');
314
315 function hide_spinner() {
316 Y.one('#subscribers-portlet-dupe-spinner').setStyle(
317 'display', 'none');
318 // Fire a custom event to signal failure, so that
319 // any remaining unsub icons can be hooked up.
320 namespace.portlet.fire('bugs:dupeportletloadfailed');
321 }
322
323 function on_success(transactionid, response, args) {
324 hide_spinner();
325
326 var dupe_subscribers_container = Y.one(
327 '#subscribers-from-duplicates-container');
328 dupe_subscribers_container.set(
329 'innerHTML',
330 dupe_subscribers_container.get('innerHTML') +
331 response.responseText);
332
333 // Fire a custom portlet loaded event to notify when
334 // it's safe to setup dupe subscriber link callbacks.
335 namespace.portlet.fire('bugs:dupeportletloaded');
336 }
337
338 var config = {on: {success: on_success,
339 failure: hide_spinner}};
340 var url = Y.one(
341 '#subscribers-from-dupes-content-link').getAttribute(
342 'href').replace('bugs.', '');
343 Y.io(url, config);
344}
345
346/*
347 * Add the user name to the subscriber's list.
348 *
349 * @method add_user_name_link
350 */
351function add_user_name_link(subscription) {
352 var person = subscription.get('person');
353 var link_node = build_user_link_html(subscription);
354 var subscribers = Y.one('#subscribers-links');
355 if (subscription.is_current_user_subscribing()) {
356 // If this is the current user, then top post the name and be done.
357 subscribers.insertBefore(link_node, subscribers.get('firstChild'));
358 } else {
359 var next = get_next_subscriber_node(subscription);
360 if (next) {
361 subscribers.insertBefore(link_node, next);
362 } else {
363 // Handle the case of the displayed "None".
364 var none_subscribers = Y.one('#none-subscribers');
365 if (none_subscribers) {
366 var none_parent = none_subscribers.get('parentNode');
367 none_parent.removeChild(none_subscribers);
368 }
369 subscribers.appendChild(link_node);
370 }
371 }
372
373 // Set the click handler if adding a remove icon.
374 if (subscription.can_be_unsubscribed_by_user()) {
375 var remove_icon =
376 Y.one('#unsubscribe-icon-' + person.get('css_name'));
377 remove_icon.on('click', function(e) {
378 e.halt();
379 unsubscribe_user_via_icon(e.target, subscription);
380 });
381 }
382}
383
384/*
385 * Unsubscribe a user from this bugtask when a remove icon is clicked.
386 *
387 * @method unsubscribe_user_via_icon
388 * @param icon {Node} The remove icon that was clicked.
389 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
390*/
391function unsubscribe_user_via_icon(icon, subscription) {
392 icon.set('src', '/@@/spinner');
393 var icon_parent = icon.get('parentNode');
394
395 var user_uri = get_user_uri_from_icon(icon);
396 var person = new Y.lp.bugs.subscriber.Subscriber({
397 uri: user_uri,
398 subscriber_ids: subscriber_ids
399 });
400 subscription.set('person', person);
401
402 // Determine if this is a dupe.
403 var is_dupe;
404 var icon_parent_div = icon_parent.get('parentNode');
405 var dupe_id = 'dupe-' + person.get('css_name');
406 if (icon_parent_div.get('id') == dupe_id) {
407 is_dupe = true;
408 } else {
409 is_dupe = false;
410 }
411
412 var error_handler = new LP.client.ErrorHandler();
413 error_handler.clearProgressUI = function () {
414 icon.set('src', '/@@/remove');
415 // Grab the icon again to reset to click handler.
416 var unsubscribe_icon = Y.one(
417 '#unsubscribe-icon-' + person.get('css_name'));
418 unsubscribe_icon.on('click', function(e) {
419 e.halt();
420 unsubscribe_user_via_icon(e.target, subscription);
421 });
422
423 };
424 error_handler.showError = function (error_msg) {
425 var flash_node = Y.one('.' + person.get('css_name'));
426 Y.lp.app.errors.display_error(flash_node, error_msg);
427
428 };
429
430 var subscription_link = subscription.get('link');
431 var config = {
432 on: {
433 success: function(client) {
434 icon_parent.removeChild(icon);
435 var anim = Y.lazr.anim.green_flash({ node: icon_parent_div });
436 anim.on('end', function(e) {
437 remove_user_name_link(icon_parent_div);
438 set_none_for_empty_subscribers();
439 var person_link = Y.one('.' + person.get('css_name'));
440 if (Y.Lang.isNull(person_link) &&
441 subscription.is_current_user_subscribing()) {
442 // Current user has been completely unsubscribed.
443 subscription.disable_spinner(
444 subscription_labels.SUBSCRIBE);
445 set_subscription_link_parent_class(
446 subscription_link, false, false);
447 subscription.set('is_direct', false);
448 subscription.set('has_dupes', false);
449 } else {
450 if (is_dupe) {
451 // A direct subscription remains.
452 set_subscription_link_parent_class(
453 subscription_link, true, false);
454 subscription.set('is_direct', true);
455 subscription.set('has_dupes', false);
456 } else {
457 // A dupe subscription remains.
458 set_subscription_link_parent_class(
459 subscription_link, false, true);
460 subscription.set('is_direct', false);
461 subscription.set('has_dupes', true);
462 }
463 }
464 });
465 anim.run();
466 },
467
468 failure: error_handler.getFailureHandler()
469 }
470 };
471
472 if (!subscription.is_current_user_subscribing()) {
473 config.parameters = {
474 person: LP.client.get_absolute_uri(user_uri)
475 };
476 }
477
478 if (is_dupe) {
479 lp_client.named_post(
480 bug_repr.self_link, 'unsubscribeFromDupes', config);
481 } else {
482 lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
483 }
484}
485
486/*
487 * Create and return a FormOverlay for advanced subscription
488 * interactions.
489 *
490 * @method setup_advanced_subscription_overlay
491 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
492 */
493function setup_advanced_subscription_overlay(subscription) {
494 var subscription_overlay = new Y.lazr.FormOverlay({
495 headerContent: '<h2>Subscribe to bug</h2>',
496 form_submit_button:
497 Y.Node.create(submit_button_html),
498 form_cancel_button:
499 Y.Node.create(cancel_button_html),
500 centered: true,
501 visible: false
502 });
503 subscription_overlay.set(
504 'form_submit_callback', function(form_data) {
505 handle_advanced_subscription_overlay(subscription, form_data);
506 subscription_overlay.hide();
507 });
508
509 var subscription_link_url = subscription.get(
510 'link').get('href') + '/++form++';
511 subscription_overlay.loadFormContentAndRender(
512 subscription_link_url);
513 subscription_overlay.render('#privacy-form-container');
514 return subscription_overlay
515}
516
517/*
518 * Subscribe the current user via the LP API.
519 *
520 * @method subscribe_current_user
521 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
522 */
523function subscribe_current_user(subscription) {
524 subscription.enable_spinner('Subscribing...');
525 var subscription_link = subscription.get('link');
526 var subscriber = subscription.get('subscriber');
527 var bug_notification_level = subscription.get('bug_notification_level');
528
529 var error_handler = new LP.client.ErrorHandler();
530 error_handler.clearProgressUI = function () {
531 subscription.disable_spinner();
532 };
533 error_handler.showError = function (error_msg) {
534 Y.lp.app.errors.display_error(subscription_link, error_msg);
535 };
536
537 var config = {
538 on: {
539 success: function(client) {
540 if (namespace.use_advanced_subscriptions) {
541 subscription.disable_spinner(
542 subscription_labels.EDIT);
543 } else {
544 subscription.disable_spinner(
545 subscription_labels.UNSUBSCRIBE);
546 }
547
548 if (subscription.has_duplicate_subscriptions()) {
549 set_subscription_link_parent_class(
550 subscription_link, true, true);
551 } else {
552 set_subscription_link_parent_class(
553 subscription_link, true, false);
554 }
555
556 // Handle the case where the subscriber's list displays
557 // "None".
558 var empty_subscribers = Y.one("#none-subscribers");
559 if (empty_subscribers) {
560 var parent = empty_subscribers.get('parentNode');
561 parent.removeChild(empty_subscribers);
562 }
563
564 add_user_name_link(subscription);
565
566 var flash_node = Y.one('.' + subscriber.get('css_name'));
567 var anim = Y.lazr.anim.green_flash({ node: flash_node });
568 anim.run();
569 },
570
571 failure: error_handler.getFailureHandler()
572 },
573
574 parameters: {
575 person: LP.client.get_absolute_uri(subscriber.get('escaped_uri')),
576 suppress_notify: false,
577 level: bug_notification_level
578 }
579 };
580 lp_client.named_post(bug_repr.self_link, 'subscribe', config);
581}
582
583/*
584 * Unsubscribe the current user via the LP API.
585 *
586 * @method unsubscribe_current_user
587 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
588 */
589function unsubscribe_current_user(subscription) {
590 subscription.enable_spinner('Unsubscribing...');
591 var subscription_link = subscription.get('link');
592 var subscriber = subscription.get('subscriber');
593
594 var error_handler = new LP.client.ErrorHandler();
595 error_handler.clearProgressUI = function () {
596 subscription.disable_spinner();
597 };
598 error_handler.showError = function (error_msg) {
599 Y.lp.app.errors.display_error(subscription_link, error_msg);
600 };
601
602 var config = {
603 on: {
604 success: function(client) {
605 if (subscription.is_direct_subscription() &&
606 subscription.has_duplicate_subscriptions()) {
607 // Don't change the 'Unsubscribe' text if
608 // dupe subscriptions remain.
609 subscription.disable_spinner();
610 set_subscription_link_parent_class(
611 subscription_link, false, true);
612 subscription.set('is_direct', false);
613 } else if (subscription.is_direct_subscription() &&
614 !subscription.has_duplicate_subscriptions()) {
615 // Only unsub'ing a direct subscriber here.
616 subscription.disable_spinner(
617 subscription_labels.SUBSCRIBE);
618 set_subscription_link_parent_class(
619 subscription_link, false, false);
620 subscription.set('is_direct', false);
621 } else {
622 // Only unsub'ing dupes here.
623 subscription.disable_spinner(
624 subscription_labels.SUBSCRIBE);
625 set_subscription_link_parent_class(
626 subscription_link, false, false);
627 subscription.set('has_dupes', false);
628 }
629
630 var flash_node = Y.one('.' + subscriber.get('css_name'));
631 var anim = Y.lazr.anim.green_flash({ node: flash_node });
632 anim.on('end', function(e) {
633 remove_user_name_link(flash_node);
634 set_none_for_empty_subscribers();
635 });
636 anim.run();
637 },
638
639 failure: error_handler.getFailureHandler()
640 }
641 };
642 if (subscription.is_direct_subscription()) {
643 lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
644 } else {
645 lp_client.named_post(
646 bug_repr.self_link, 'unsubscribeFromDupes', config);
647 }
648}
649
650/*
651 * Initialize click handler for the subscribe someone else link.
652 *
653 * @method setup_subscribe_someone_else_handler
654 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
655 */
656function setup_subscribe_someone_else_handler(subscription) {
657 var config = {
658 header: 'Subscribe someone else',
659 step_title: 'Search',
660 picker_activator: '.menu-link-addsubscriber'
661 };
662
663 config.save = function(result) {
664 subscribe_someone_else(result, subscription);
665 };
666 var picker = Y.lp.app.picker.create('ValidPersonOrTeam', config);
667}
668
669/*
670 * Build the HTML for a user link for the subscribers list.
671 *
672 * @method build_user_link_html
673 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
674 * @return html {String} The HTML used for creating a subscriber link.
675 */
676function build_user_link_html(subscription) {
677 var name = subscription.get('person').get('name');
678 var css_name = subscription.get('person').get('css_name');
679 var full_name = subscription.get('person').get('full_display_name');
680 // Be paranoid about display_name, since timeouts or other errors
681 // could mean display_name wasn't set on initialization.
682 if (subscription.get('person').get('display_name') === '') {
683 subscription.get('person').set_display_name();
684 }
685 var display_name = subscription.get('person').get('display_name');
686 var terms = {
687 name: name,
688 css_name: css_name,
689 display_name: display_name,
690 full_name: full_name
691 };
692
693 if (subscription.is_current_user_subscribing()) {
694 terms.subscribed_by = 'themselves';
695 } else {
696 terms.subscribed_by = 'by ' + full_name;
697 }
698
699 var html = Y.Node.create('<div><a></a></div>');
700 html.addClass(terms.css_name);
701
702 if (subscription.is_direct_subscription()) {
703 html.set('id', 'direct-' + terms.css_name);
704 } else {
705 html.set('id', 'dupe-' + terms.css_name);
706 }
707
708 html.one('a')
709 .set('href', '/~' + terms.name)
710 .set('name', terms.full_name)
711 .set('title', 'Subscribed ' + terms.subscribed_by);
712
713 var span;
714 if (subscription.is_team()) {
715 span = '<span class="sprite team"></span>';
716 } else {
717 span = '<span class="sprite person"></span>';
718 }
719
720 html.one('a')
721 .appendChild(Y.Node.create(span))
722 .appendChild(document.createTextNode(terms.display_name));
723
724 // Add remove icon if the current user can unsubscribe the subscriber.
725 if (subscription.can_be_unsubscribed_by_user()) {
726 var icon_html = Y.Node.create(
727 '<a href="+subscribe">' +
728 '<img class="unsub-icon" src="/@@/remove" alt="Remove" /></a>');
729 icon_html
730 .set('id', 'unsubscribe-' + terms.css_name)
731 .set('title', 'Unsubscribe ' + terms.full_name);
732 icon_html.one('img')
733 .set('id', 'unsubscribe-icon-' + terms.css_name);
734 html.appendChild(icon_html);
735 }
736
737 return html;
738}
739
740/*
741 * Returns the next node in alphabetical order after the subscriber
742 * node now being added. No node is returned to append to end of list.
743 *
744 * The name can appear in one of two different lists. 1) The list of
745 * subscribers that can be unsubscribed by the current user, and
746 * 2) the list of subscribers that cannot be unsubscribed.
747 *
748 * @method get_next_subscriber_node
749 * @param subscription_link {Node} The sub/unsub link.
750 * @return {Node} The node appearing next in the subscriber list or
751 * undefined if no node is next.
752 */
753function get_next_subscriber_node(subscription) {
754 var full_name = subscription.get('person').get('full_display_name');
755 var can_be_unsubscribed = subscription.can_be_unsubscribed_by_user();
756 var nodes_by_name = {};
757 var unsubscribables = [];
758 var not_unsubscribables = [];
759
760 // Use the list of subscribers pulled from the DOM to have sortable
761 // lists of unsubscribable vs. not unsubscribale person links.
762 var all_subscribers = Y.all('#subscribers-links div');
763 if (all_subscribers.size() > 0) {
764 all_subscribers.each(function(sub_link) {
765 if (sub_link.getAttribute('id') != 'temp-username') {
766 // User's displayname is found via the link's "name"
767 // attribute.
768 var sub_link_name = sub_link.one('a').getAttribute('name');
769 nodes_by_name[sub_link_name] = sub_link;
770 if (sub_link.one('img.unsub-icon')) {
771 unsubscribables.push(sub_link_name);
772 } else {
773 not_unsubscribables.push(sub_link_name);
774 }
775 }
776 });
777
778 // Add the current subscription.
779 if (can_be_unsubscribed) {
780 unsubscribables.push(full_name);
781 } else {
782 not_unsubscribables.push(full_name);
783 }
784 unsubscribables.sort();
785 not_unsubscribables.sort();
786 } else {
787 // If there is no all_subscribers, then we're dealing with
788 // the printed None, so return.
789 return;
790 }
791
792 var i;
793 if ((!unsubscribables && !not_unsubscribables) ||
794 // If A) neither list exists, B) the user belongs in the second
795 // list but the second list doesn't exist, or C) user belongs in the
796 // first list and the second doesn't exist, return no node to append.
797 (!can_be_unsubscribed && !not_unsubscribables) ||
798 (can_be_unsubscribed && unsubscribables && !not_unsubscribables)) {
799 return;
800 } else if (
801 // If the user belongs in the first list, and the first list
802 // doesn't exist, but the second one does, return the first node
803 // in the second list.
804 can_be_unsubscribed && !unsubscribables && not_unsubscribables) {
805 return nodes_by_name[not_unsubscribables[0]];
806 } else if (can_be_unsubscribed) {
807 // If the user belongs in the first list, loop the list for position.
808 for (i=0; i<unsubscribables.length; i++) {
809 if (unsubscribables[i] == full_name) {
810 if (i+1 < unsubscribables.length) {
811 return nodes_by_name[unsubscribables[i+1]];
812 // If the current link should go at the end of the first
813 // list and we're at the end of that list, return the
814 // first node of the second list. Due to earlier checks
815 // we can be sure this list exists.
816 } else if (i+1 >= unsubscribables.length) {
817 return nodes_by_name[not_unsubscribables[0]];
818 }
819 }
820 }
821 } else if (!can_be_unsubscribed) {
822 // If user belongs in the second list, loop the list for position.
823 for (i=0; i<not_unsubscribables.length; i++) {
824 if (not_unsubscribables[i] == full_name) {
825 if (i+1 < not_unsubscribables.length) {
826 return nodes_by_name[not_unsubscribables[i+1]];
827 } else {
828 return;
829 }
830 }
831 }
832 }
833}
834
835/*
836 * Traverse the DOM of a given remove icon to find
837 * the user's link. Returns a URI of the form "/~username".
838 *
839 * @method get_user_uri_from_icon
840 * @param icon {Node} The node representing a remove icon.
841 * @return user_uri {String} The user's uri, without the hostname.
842 */
843function get_user_uri_from_icon(icon) {
844 var parent_div = icon.get('parentNode').get('parentNode');
845 // This should be parent_div.firstChild, but because of #text
846 // and cross-browser issues, using the YUI query syntax is
847 // safer here.
848 var user_uri = parent_div.one('a').getAttribute('href');
849
850 // Strip the domain off. We just want a path.
851 var host_start = user_uri.indexOf('//');
852 if (host_start != -1) {
853 var host_end = user_uri.indexOf('/', host_start+2);
854 return user_uri.substring(host_end, user_uri.length);
855 }
856
857 return user_uri;
858}
859
860/*
861 * Used to remove the user's name from the subscriber's list.
862 *
863 * @method remove_user_name_link
864 * @param user_node {Node} Node representing the user name link.
865 */
866function remove_user_name_link(user_node) {
867 var parent = user_node.get('parentNode');
868 parent.removeChild(user_node);
869}
870
871/*
872 * Add the "None" div to the subscribers list if
873 * there aren't any subscribers left.
874 *
875 * @method set_none_for_empty_subscribers
876 */
877function set_none_for_empty_subscribers() {
878 var subscriber_list = Y.one('#subscribers-links');
879 // Assume if subscriber_list has no child divs
880 // then the list of subscribers is empty.
881 if (!Y.Lang.isValue(subscriber_list.one('div')) &&
882 !Y.Lang.isValue(Y.one('#none-subscribers'))) {
883 var none_div = Y.Node.create('<div id="none-subscribers">None</div>');
884 subscriber_list.appendChild(none_div);
885 }
886
887 // Clear the empty duplicate subscribers list if it exists.
888 var dup_list = Y.one('#subscribers-from-duplicates');
889 if (Y.Lang.isValue(dup_list) &&
890 !Y.Lang.isValue(dup_list.one('div'))) {
891 var parent = dup_list.get('parentNode');
892 parent.removeChild(dup_list);
893 }
894}
895
896/*
897 * Set the class on subscription link's parentNode.
898 *
899 * This is used to reset the class used by the
900 * click handler to know which link was clicked.
901 *
902 * @method set_subscription_link_parent_class
903 * @param subscription_link {Node} The sub/unsub link.
904 * @param subscribed {Boolean} The sub/unsub'ed flag for the class.
905 * @param dupe_subscribed {Boolean} The sub/unsub'ed flag for dupes
906 * on the class.
907 */
908function set_subscription_link_parent_class(
909 user_link, subscribed, dupe_subscribed) {
910
911 var parent = user_link.get('parentNode');
912 if (subscribed) {
913 parent.removeClass('subscribed-false');
914 parent.addClass('subscribed-true');
915 } else {
916 parent.removeClass('subscribed-true');
917 parent.addClass('subscribed-false');
918 }
919
920 if (dupe_subscribed) {
921 parent.removeClass('dup-subscribed-false');
922 parent.addClass('dup-subscribed-true');
923 } else {
924 parent.removeClass('dup-subscribed-true');
925 parent.addClass('dup-subscribed-false');
926 }
927}
928
929
930/*
931 * Handle the advanced_subscription_overlay's form submissions.
932 *
933 * @method handle_advanced_subscription_overlay
934 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
935 * @param form_data {Object} The data from the submitted form.
936 */
937function handle_advanced_subscription_overlay(subscription, form_data) {
938 var link = subscription.get('link');
939 var link_parent = link.get('parentNode');
940 if (link_parent.hasClass('subscribed-false') &&
941 link_parent.hasClass('dup-subscribed-false')) {
942 // The user isn't subscribed, so subscribe them.
943 subscription.set(
944 'bug_notification_level',
945 form_data['field.bug_notification_level']);
946 subscribe_current_user(subscription);
947 } else if (
948 form_data['field.subscription'] == 'update-subscription') {
949 // The user is already subscribed and wants to update their
950 // subscription.
951 setup_client_and_bug();
952 var person_name = subscription.get('person').get('name');
953 var subscription_url =
954 lp_bug_entry.get('self_link') + '/+subscription/' +
955 person_name;
956 config = {
957 on: {
958 success: function(lp_subscription) {
959 subscription.enable_spinner('Updating subscription...');
960 lp_subscription.set(
961 'bug_notification_level',
962 form_data['field.bug_notification_level'][0])
963 save_config = {
964 on: {
965 success: function(e) {
966 subscription.disable_spinner(
967 'Edit subscription');
968 var anim = Y.lazr.anim.green_flash({
969 node: link_parent
970 });
971 anim.run();
972 },
973 failure: function(e) {
974 subscription.disable_spinner(
975 'Edit subscription');
976 var anim = Y.lazr.anim.red_flash({
977 node: link_parent
978 });
979 anim.run();
980 }
981 }
982 }
983 lp_subscription.lp_save(save_config);
984 }
985 }
986 }
987 lp_client.get(subscription_url, config);
988 } else {
989 // The user is already subscribed and wants to unsubscribe.
990 unsubscribe_current_user(subscription);
991 }
992}
993
994/*
995 * Subscribe a person or team other than the current user.
996 * This is a callback for the subscribe someone else picker.
997 *
998 * @method subscribe_someone_else
999 * @result {Object} The object representing a person returned by the API.
1000 */
1001function subscribe_someone_else(result, subscription) {
1002 var person = new Y.lp.bugs.subscriber.Subscriber({
1003 uri: result.api_uri,
1004 display_name: result.title,
1005 subscriber_ids: subscriber_ids
1006 });
1007 subscription.set('person', person);
1008
1009 var error_handler = new LP.client.ErrorHandler();
1010 error_handler.showError = function(error_msg) {
1011 Y.lp.app.errors.display_error(
1012 Y.one('.menu-link-addsubscriber'), error_msg);
1013 };
1014
1015 if (subscription.is_already_subscribed()) {
1016 error_handler.showError(
1017 subscription.get('person').get('full_display_name') +
1018 ' has already been subscribed');
1019 } else {
1020 check_can_be_unsubscribed(subscription);
1021 }
1022}
1023
1024/*
1025 * Check if the current user can unsubscribe the person
1026 * being subscribed.
1027 *
1028 * This must be done in JavaScript, since the subscription
1029 * hasn't completed yet, and so, can_be_unsubscribed_by_user
1030 * cannot be used.
1031 *
1032 * @method check_can_be_unsubscribed
1033 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
1034 */
1035function check_can_be_unsubscribed(subscription) {
1036 var error_handler = new LP.client.ErrorHandler();
1037 error_handler.showError = function (error_msg) {
1038 Y.lp.app.errors.display_error(
1039 Y.one('.menu-link-addsubscriber'), error_msg);
1040 };
1041
1042 var config = {
1043 on: {
1044 success: function(result) {
1045 var is_team = result.get('is_team');
1046 subscription.set('is_team', is_team);
1047 var final_config = {
1048 on: {
1049 success: function(result) {
1050 var team_member = false;
1051 for (var i=0; i<result.entries.length; i++) {
1052 if (result.entries[i].member_link ==
1053 LP.client.get_absolute_uri(
1054 subscription.get(
1055 'subscriber').get('uri'))) {
1056 team_member = true;
1057 }
1058 }
1059
1060 if (team_member) {
1061 subscription.set('can_be_unsubscribed', true);
1062 add_temp_user_name(subscription);
1063 } else {
1064 subscription.set(
1065 'can_be_unsubscribed', false);
1066 add_temp_user_name(subscription);
1067 }
1068 },
1069
1070 failure: error_handler.getFailureHandler()
1071 }
1072 };
1073
1074 if (is_team) {
1075 // Get a list of members to see if current user
1076 // is a team member.
1077 var members = result.get(
1078 'members_details_collection_link');
1079 lp_client.get(members, final_config);
1080 } else {
1081 subscription.set('can_be_unsubscribed', false);
1082 add_temp_user_name(subscription);
1083 }
1084 },
1085
1086 failure: error_handler.getFailureHandler()
1087 }
1088 };
1089 lp_client.get(LP.client.get_absolute_uri(
1090 subscription.get('person').get('escaped_uri')), config);
1091}
1092
1093/*
1094 * Add a grayed out, temporary user name when subscribing
1095 * someone else.
1096 *
1097 * @method add_temp_user_name
1098 * @param subscription_link {Node} The sub/unsub link.
1099 */
1100function add_temp_user_name(subscription) {
1101 // Be paranoid about display_name, since timeouts or other errors
1102 // could mean display_name wasn't set on initialization.
1103 if (subscription.get('person').get('display_name') === '') {
1104 subscription.get('person').set_display_name();
1105 }
1106 var display_name = subscription.get('person').get('display_name');
1107 var img_src;
1108 if (subscription.is_team()) {
1109 img_src = '/@@/teamgray';
1110 } else {
1111 img_src = '/@@/persongray';
1112 }
1113
1114 // The <span>...</span> below must *not* be <span/>. On FF (maybe
1115 // others, but at least on FF 3.0.11) will then not notice any
1116 // following sibling nodes, like the spinner image.
1117 var link_node = Y.Node.create([
1118 '<div id="temp-username"> ',
1119 ' <img alt="" width="14" height="14" />',
1120 ' <span>Other Display Name</span>',
1121 ' <img id="temp-name-spinner" src="/@@/spinner" alt="" ',
1122 ' style="position:absolute;right:8px" /></div>'].join(''));
1123 link_node.one('img').set('src', img_src);
1124 link_node.replaceChild(
1125 document.createTextNode(display_name),
1126 link_node.one('span'));
1127
1128 var subscribers = Y.one('#subscribers-links');
1129 var next = get_next_subscriber_node(subscription);
1130 if (next) {
1131 subscribers.insertBefore(link_node, next);
1132 } else {
1133 // Handle the case of the displayed "None".
1134 var none_subscribers = Y.one('#none-subscribers');
1135 if (none_subscribers) {
1136 var none_parent = none_subscribers.get('parentNode');
1137 none_parent.removeChild(none_subscribers);
1138 }
1139 subscribers.appendChild(link_node);
1140 }
1141
1142 // Fire a custom event to know it's safe to begin
1143 // any actual subscribing work.
1144 namespace.portlet.fire('bugs:nameloaded', subscription);
1145}
1146
1147}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base",
1148 "json-parse", "substitute", "widget-position-ext",
1149 "lazr.formoverlay", "lazr.anim", "lazr.base",
1150 "lazr.overlay", "lazr.choiceedit", "lp.app.picker",
1151 "lp.client.plugins", "lp.bugs.subscriber",
1152 "lp.app.errors"]});
01153
=== modified file 'lib/lp/bugs/templates/bug-portlet-subscribers.pt'
--- lib/lp/bugs/templates/bug-portlet-subscribers.pt 2010-06-15 13:51:55 +0000
+++ lib/lp/bugs/templates/bug-portlet-subscribers.pt 2011-02-15 18:19:47 +0000
@@ -25,7 +25,7 @@
25 <img src="/@@/spinner" />25 <img src="/@@/spinner" />
26 </div>26 </div>
27 <script type="text/javascript">27 <script type="text/javascript">
28 LPS.use('io-base', 'node', 'lp.bugs.bugtask_index', function(Y) {28 LPS.use('io-base', 'node', 'lp.bugs.bugtask_index.portlets', function(Y) {
29 // Must be done inline here to ensure the load event fires.29 // Must be done inline here to ensure the load event fires.
30 // This is a work around for a YUI3 issue with event handling.30 // This is a work around for a YUI3 issue with event handling.
31 var subscription_link = Y.one('.menu-link-subscription');31 var subscription_link = Y.one('.menu-link-subscription');
@@ -37,8 +37,8 @@
37 }37 }
3838
39 Y.on('domready', function() {39 Y.on('domready', function() {
40 if (Y.lp.bugs.bugtask_index) {40 if (Y.lp.bugs.bugtask_index.portlets) {
41 Y.lp.bugs.bugtask_index.load_subscribers_portlet(41 Y.lp.bugs.bugtask_index.portlets.load_subscribers_portlet(
42 subscription_link, subscription_link_handler);42 subscription_link, subscription_link_handler);
43 }43 }
44 });44 });
4545
=== modified file 'lib/lp/bugs/templates/bugtask-index.pt'
--- lib/lp/bugs/templates/bugtask-index.pt 2011-02-07 11:44:51 +0000
+++ lib/lp/bugs/templates/bugtask-index.pt 2011-02-15 18:19:47 +0000
@@ -23,8 +23,9 @@
23 </tal:subscriptions-js>23 </tal:subscriptions-js>
24 <script type="text/javascript">24 <script type="text/javascript">
25 LPS.use('base', 'node', 'oop', 'event', 'lp.bugs.bugtask_index',25 LPS.use('base', 'node', 'oop', 'event', 'lp.bugs.bugtask_index',
26 'lp.bugs.bugtask_index.portlets',
26 'lp.code.branchmergeproposal.diff', function(Y) {27 'lp.code.branchmergeproposal.diff', function(Y) {
27 Y.lp.bugs.bugtask_index.use_advanced_subscriptions =28 Y.lp.bugs.bugtask_index.portlets.use_advanced_subscriptions =
28 use_advanced_subscriptions;29 use_advanced_subscriptions;
29 Y.lp.bugs.bugtask_index.setup_bugtask_index();30 Y.lp.bugs.bugtask_index.setup_bugtask_index();
30 Y.on('load', function(e) {31 Y.on('load', function(e) {