18
14
* @param {String} attribute_name The attribute on the resource being
20
16
* @param {String} content_box_id
21
* @param {Object} config Object literal of config name/value pairs. The
22
* values listed below are common for all picker types.
23
* config.picker_type: the type of picker to create (default or person).
17
* @param {Object} config Object literal of config name/value pairs.
24
18
* config.header: a line of text at the top of the widget.
25
19
* config.step_title: overrides the subtitle.
20
* config.remove_button_text: Override the default 'Remove' text.
26
21
* config.null_display_value: Override the default 'None' text.
22
* config.show_remove_button: Should the remove button be shown?
23
* Defaults to false, should be a boolean.
24
* config.show_assign_me_botton: Should the 'assign me' button be shown?
25
* Defaults to false, should be a boolean.
27
26
* config.show_search_box: Should the search box be shown.
28
27
* Vocabularies that are not huge should not have a search box.
37
var show_remove_button = false;
38
var show_assign_me_button = false;
39
var remove_button_text = 'Remove';
38
40
var null_display_value = 'None';
39
41
var show_search_box = true;
40
var vocabulary_filters;
42
42
resource_uri = Y.lp.client.normalize_uri(resource_uri);
44
44
if (config !== undefined) {
45
if (config.remove_button_text !== undefined) {
46
remove_button_text = config.remove_button_text;
45
49
if (config.null_display_value !== undefined) {
46
50
null_display_value = config.null_display_value;
53
if (config.show_remove_button !== undefined) {
54
show_remove_button = config.show_remove_button;
57
if (config.show_assign_me_button !== undefined) {
58
show_assign_me_button = config.show_assign_me_button;
48
61
if (config.show_search_box !== undefined) {
49
62
show_search_box = config.show_search_box;
51
vocabulary_filters = config.vocabulary_filters;
54
66
var content_box = Y.one('#' + content_box_id);
79
var show_hide_buttons = function () {
80
var link = content_box.one('.yui3-activator-data-box a');
82
if (link === null || !show_remove_button) {
83
remove_button.addClass('yui-picker-hidden');
85
remove_button.removeClass('yui-picker-hidden');
89
if (assign_me_button) {
91
&& link.get('href').indexOf(LP.links.me + '/') != -1) {
92
assign_me_button.addClass('yui-picker-hidden');
94
assign_me_button.removeClass('yui-picker-hidden');
67
99
var save = function (picker_result) {
68
100
activator.renderProcessing();
69
101
var success_handler = function (entry) {
70
var to_render = null_display_value;
71
var selected_value = null;
72
if (entry.get(attribute_name) !== null) {
73
to_render = entry.getHTML(attribute_name);
74
selected_value = picker_result.api_uri;
76
// NB We need to set the selected_value_metadata attribute first
77
// because we listen for changes to selected_value.
78
picker.set('selected_value_metadata', picker_result.metadata);
79
picker.set('selected_value', selected_value);
80
activator.renderSuccess(to_render);
83
var patch_payload = {};
84
if (Y.Lang.isValue(picker_result.api_uri)) {
85
patch_payload[attribute_name] = Y.lp.client.get_absolute_uri(
86
picker_result.api_uri);
88
patch_payload[attribute_name] = null;
91
var client = new Y.lp.client.Launchpad();
102
activator.renderSuccess(entry.getHTML(attribute_name));
106
var patch_payload = {};
107
patch_payload[attribute_name] = Y.lp.client.get_absolute_uri(
108
picker_result.api_uri);
110
var client = new Y.lp.client.Launchpad();
111
client.patch(picker._resource_uri, patch_payload, {
112
accept: 'application/json;include=lp_html',
114
success: success_handler,
115
failure: failure_handler
120
var assign_me = function () {
129
var remove = function () {
131
activator.renderProcessing();
132
var success_handler = function (entry) {
133
activator.renderSuccess(Y.Node.create(null_display_value));
137
var patch_payload = {};
138
patch_payload[attribute_name] = null;
140
var client = new Y.lp.client.Launchpad();
141
// Use picker._resource_uri, since it might have been changed
142
// from the outside after the widget has already been initialized.
92
143
client.patch(picker._resource_uri, patch_payload, {
93
144
accept: 'application/json;include=lp_html',
101
152
config.save = save;
102
var picker = namespace.create(
103
vocabulary, config, undefined, vocabulary_filters);
153
var picker = namespace.create(vocabulary, config, activator);
104
154
picker._resource_uri = resource_uri;
155
var extra_buttons = Y.Node.create('<div class="extra-form-buttons"/>');
156
var remove_button, assign_me_button;
157
if (show_remove_button) {
158
remove_button = Y.Node.create(
159
'<a class="yui-picker-remove-button bg-image" ' +
160
'href="javascript:void(0)" ' +
161
'style="background-image: url(/@@/remove); padding-right: 1em">' +
162
remove_button_text + '</a>');
163
remove_button.on('click', remove);
164
extra_buttons.appendChild(remove_button);
166
if (show_assign_me_button) {
167
assign_me_button = Y.Node.create(
168
'<a class="yui-picker-assign-me-button bg-image" ' +
169
'href="javascript:void(0)" ' +
170
'style="background-image: url(/@@/person)">' +
172
assign_me_button.on('click', assign_me);
173
extra_buttons.appendChild(assign_me_button);
175
picker.set('footer_slot', extra_buttons);
106
177
// If we are to pre-load the vocab, we need a spinner.
107
178
// We set it up here because we only want to do it once and the
116
187
if (!show_search_box) {
117
188
config.temp_spinner.removeClass('unseen');
118
189
picker.set('min_search_chars', 0);
119
picker.fire(Y.lazr.picker.Picker.SEARCH, '');
120
picker.get('contentBox').one(
121
'.yui3-picker-search-box').addClass('unseen');
190
picker.fire('search', '');
191
picker.get('contentBox').one('.yui3-picker-search-box').addClass('unseen');
125
195
activator.render();
200
* A specific instantiation of the yesno warning for private teams, used in
203
namespace.public_private_warning = function(
204
picker, picker_result, do_save, do_cancel) {
206
var client = new Y.lp.client.Launchpad();
209
success: function (person) {
210
var private_person = person.get('private');
211
var public_context = true;
212
if (LP.cache.context.private !== undefined) {
213
public_context = !(LP.cache.context.private);
216
if (private_person && public_context) {
218
"<p>This action will reveal this team's name to " +
220
Y.lp.app.picker.yesno_save_confirmation(
221
picker, yesno_content, 'Continue', 'Choose Again',
229
client.get(picker_result.api_uri, config);
233
273
* Insert the validation content into the form and animate its appearance.
235
275
function animate_validation_content(picker, validation_content) {
250
290
picker.get('contentBox').all('.steps').show();
251
291
var validation_node = picker.get('contentBox').one('.validation-node');
252
292
var content_node = picker.get('contentBox').one('.yui3-widget-bd');
253
if (validation_node !== null) {
293
if (validation_node != null) {
254
294
validation_node.get('parentNode').removeChild(validation_node);
255
295
content_node.addClass('transparent');
256
296
content_node.setStyle('opacity', 0);
273
* Connect the onchange event of the select menu to copy the selected value
276
* @param {Node} select_menu The select menu with suggested matches.
277
* @param {Node} text_input The input field to copy the selected match too.
279
namespace.connect_select_menu = function (select_menu, text_input) {
280
if (Y.Lang.isValue(select_menu)) {
281
var copy_selected_value = function(e) {
282
text_input.value = select_menu.value;
284
Y.on('change', copy_selected_value, select_menu);
289
312
* Creates a picker widget that has already been rendered and hidden.
291
* @requires dom, dump, lazr.overlay
314
* @requires dom, dump, lazr.overlay, lazr.picker
293
316
* @param {String} vocabulary Name of the vocabulary to query.
294
317
* @param {Object} config Optional Object literal of config name/value pairs.
297
320
* config.step_title overrides the subtitle.
298
321
* config.save is a Function (optional) which takes
299
322
* a single string argument.
300
* config.show_search_box: Should the search box be
302
* @param {String} associated_field_id Optional Id of the text field to
303
* to be updated with the value selected by the
305
* @param {Object} vocabulary_filters Optional List of filters which are
306
* supported by the vocabulary. Filter objects are a
307
* dict of name, title, description values.
310
namespace.create = function (vocabulary, config, associated_field_id,
311
vocabulary_filters) {
324
namespace.create = function (vocabulary, config, activator) {
316
329
var header = 'Choose an item.';
317
330
var step_title = "Enter search terms";
318
var show_search_box = true;
319
var picker_type = "default";
320
331
if (config !== undefined) {
321
332
if (config.header !== undefined) {
322
333
header = config.header;
325
336
if (config.step_title !== undefined) {
326
337
step_title = config.step_title;
329
if (config.show_search_box !== undefined) {
330
show_search_box = config.show_search_box;
333
if (config.picker_type !== undefined) {
334
picker_type = config.picker_type;
340
if (typeof vocabulary !== 'string' && typeof vocabulary !== 'object') {
341
if (typeof vocabulary != 'string' && typeof vocabulary != 'object') {
341
342
throw new TypeError(
342
343
"vocabulary argument for Y.lp.picker.create() must be a " +
343
344
"string or associative array: " + vocabulary);
346
347
var new_config = Y.merge(config, {
347
associated_field_id: associated_field_id,
349
349
points: [Y.WidgetPositionAlign.CC,
350
350
Y.WidgetPositionAlign.CC]
354
354
headerContent: "<h2>" + header + "</h2>",
355
355
steptitle: step_title,
358
filter_options: vocabulary_filters
362
if (picker_type === 'person') {
363
picker = new Y.lazr.picker.PersonPicker(new_config);
365
picker = new Y.lazr.picker.Picker(new_config);
368
// We don't want the default save to fire since this hides
359
var picker = new Y.lazr.Picker(new_config);
361
// We don't want the Y.lazr.Picker default save to fire since this hides
369
362
// the form. We want to do this ourselves after any validation has had a
370
363
// chance to be performed.
371
364
picker.publish('save', { defaultFn: function(){} } );
373
// Has the user performed a search yet?
374
var user_has_searched = false;
376
366
picker.subscribe('save', function (e) {
377
367
Y.log('Got save event.');
378
var picker_result = e.details[Y.lazr.picker.Picker.SAVE_RESULT];
368
var picker_result = e.details[Y.lazr.Picker.SAVE_RESULT];
379
369
var do_save = function() {
380
user_has_searched = false;
382
370
if (Y.Lang.isFunction(config.save)) {
383
371
config.save(picker_result);
396
384
picker.subscribe('cancel', function (e) {
397
385
Y.log('Got cancel event.');
398
386
reset_form(picker);
399
user_has_searched = false;
402
if (Y.Lang.isValue(config.extra_no_results_message)) {
403
picker.before('resultsChange', function (e) {
404
var new_results = e.details[0].newVal;
405
if (new_results.length === 0) {
406
picker.set('footer_slot',
407
Y.Node.create(config.extra_no_results_message));
409
picker.set('footer_slot', null);
414
389
// Search for results, create batches and update the display.
415
390
// in the widget.
416
391
var search_handler = function (e) {
417
392
Y.log('Got search event:' + Y.dump(e.details));
418
393
var search_text = e.details[0];
419
394
var selected_batch = e.details[1] || 0;
420
// Was this search initiated automatically, perhaps to load
422
var automated_search = e.details[2] || false;
423
var search_filter = e.details[3];
424
395
var start = BATCH_SIZE * selected_batch;
427
// Record whether or not the user has initiated a search yet.
428
user_has_searched = user_has_searched || !automated_search;
430
396
var display_vocabulary = function(results, total_size, start) {
431
var max_size = MAX_BATCHES * BATCH_SIZE;
432
if (show_search_box && total_size > max_size) {
397
if (total_size > (MAX_BATCHES * BATCH_SIZE)) {
433
398
picker.set('error',
434
399
'Too many matches. Please try to narrow your search.');
435
400
// Display a single empty result item so that the picker
459
424
// We can pass in a vocabulary name
460
if (typeof vocabulary === 'string') {
425
if (typeof vocabulary == 'string') {
461
426
var success_handler = function (ignore, response, args) {
462
427
var entry = Y.JSON.parse(response.responseText);
463
428
var total_size = entry.total_size;
464
429
var start = entry.start;
465
430
var results = entry.entries;
466
431
hide_temporary_spinner(config.temp_spinner);
467
// If this was an automated (preemptive) search and the user
468
// has not subsequently initiated their own search, display
469
// the results of the search.
470
if (user_has_searched !== automated_search) {
471
display_vocabulary(results, total_size, start);
475
var failure_handler = function (ignore, response, args) {
476
Y.log("Loading " + uri + " failed.");
477
hide_temporary_spinner(config.temp_spinner);
478
// If this was an automated (preemptive) search and the user
479
// has subsequently initiated their own search, don't bother
480
// showing an error message about something the user didn't
481
// initiate and now doesn't care about.
482
if (user_has_searched === automated_search) {
486
"Sorry, something went wrong with your search.";
487
if (response.status === 500) {
489
" We've recorded what happened, and we'll fix it " +
490
"as soon as possible.";
491
} else if (response.status >= 502 && response.status <= 504) {
493
" Trying again in a couple of minutes might work.";
495
var oops_id = response.getResponseHeader('X-Lazr-OopsId');
497
base_error += ' (Error ID: ' + oops_id + ')';
499
picker.set('error', base_error);
500
picker.set('search_mode', false);
432
display_vocabulary(results, total_size, start);
505
436
qs = Y.lp.client.append_qs(qs, 'name', vocabulary);
506
437
qs = Y.lp.client.append_qs(qs, 'search_text', search_text);
507
if (Y.Lang.isValue(search_filter)) {
508
qs = Y.lp.client.append_qs(
509
qs, 'search_filter', search_filter);
511
438
qs = Y.lp.client.append_qs(qs, 'batch', BATCH_SIZE);
512
439
qs = Y.lp.client.append_qs(qs, 'start', start);
516
443
// use the context to limit the results to the same project.
519
if (Y.Lang.isValue(config.context)){
520
uri += Y.lp.get_url_path(
521
config.context.get('web_link')) + '/';
446
if (Y.Lang.isValue(config['context'])){
447
uri += Y.lp.get_url_path(config['context'].get('web_link')) + '/';
523
449
uri += '@@+huge-vocabulary?' + qs;
525
var yio = (config.yio !== undefined) ? config.yio : Y;
527
452
headers: {'Accept': 'application/json'},
530
455
success: success_handler,
531
failure: failure_handler
456
failure: function (arg) {
457
hide_temporary_spinner(config.temp_spinner);
458
picker.set('error', 'Loading results failed.');
459
picker.set('search_mode', false);
460
Y.log("Loading " + uri + " failed.");
534
464
// Or we can pass in a vocabulary directly.
547
477
}, "0.1", {"requires": [
548
"io", "dom", "dump", "event", "lazr.activator", "json-parse",
549
"lp.client", "lazr.picker", "lazr.person-picker"
478
"io", "dom", "dump", "lazr.picker", "lazr.activator", "json-parse",