15
15
var namespace = Y.namespace('lp.registry.distroseries.initseries');
19
* A form row matching that which LaunchpadForm presents, containing a
20
* field (defined in a subclass), and an optional label and
23
* @class FormRowWidget
25
var FormRowWidget = function() {
26
FormRowWidget.superclass.constructor.apply(this, arguments);
29
Y.mix(FormRowWidget, {
31
NAME: 'formRowWidget',
41
setter: function(value, name) {
42
this.fieldNode.all("input, select").set("name", value);
47
* The top label for the field.
53
return this.labelNode.get("text");
55
setter: function(value, name) {
56
this.labelNode.set("text", value);
61
* A dictionary {link:link, text:text} to populate
62
* the pop-up help for the field.
68
return {link:this.helpNode.one('a')
71
.one('.invisible-link')
74
setter: function(value, name) {
75
if ((value.link !== undefined) &&
76
(value.text !== undefined)) {
77
this.helpNode.one('a').set("href", value.link);
78
this.helpNode.one('.invisible-link')
79
.set("text", value.text);
80
this.helpNode.removeClass('unseen');
83
this.helpNode.addClass('unseen');
89
* A description shown near the field.
95
return this.descriptionNode.get("text");
97
setter: function(value, name) {
98
this.descriptionNode.set("text", value);
105
Y.extend(FormRowWidget, Y.Widget, {
107
BOUNDING_TEMPLATE: "<tr></tr>",
109
CONTENT_TEMPLATE: '<td colspan="2"></td>',
111
initializer: function(config) {
112
this.labelNode = Y.Node.create("<label />");
113
this.helpNode = Y.Node.create(('<span class="helper unseen">'+
115
'target="help" class="sprite maybe"> ' +
116
'<span class="invisible-link"></span></a></span>'));
117
this.fieldNode = Y.Node.create("<div></div>");
118
this.descriptionNode = Y.Node.create('<p class="formHelp" />');
119
this.spinnerNode = Y.Node.create(
120
'<img src="/@@/spinner" alt="Loading..." />');
123
renderUI: function() {
124
this.get("contentBox")
125
.append(this.labelNode)
126
.append(this.helpNode)
127
.append(this.fieldNode)
128
.append(this.descriptionNode);
134
* @method showSpinner
136
showSpinner: function() {
137
this.fieldNode.empty().append(this.spinnerNode);
143
* @method hideSpinner
145
hideSpinner: function() {
146
this.spinnerNode.remove();
154
showError: function(error) {
155
var message = Y.Node.create('<p />').set("text", error);
156
this.fieldNode.empty().append(message);
157
Y.lazr.anim.red_flash({node: message}).run();
162
namespace.FormRowWidget = FormRowWidget;
165
* A table to display, order, delete the selected parent series. Each parent
166
* can also be made an overlay, and a component and a pocket selected.
169
var ParentSeriesListWidget = function() {
170
ParentSeriesListWidget
171
.superclass.constructor.apply(this, arguments);
174
Y.mix(ParentSeriesListWidget, {
176
NAME: 'parentSeriesListWidget',
181
* The DistroSeries the choices in this field should
182
* reflect. Takes the form of a list of ids,
190
this.fieldNode.all("tbody > tr.parent").each(
193
node.get('id').replace('parent-',''));
202
this.fieldNode.all("tbody > tr.parent").each(
205
node.one('input.overlay').get('checked'));
213
var overlay_pockets = [];
214
this.fieldNode.all("tbody > tr.parent").each(
216
var select = node.one('td.pocket').one('select');
217
if (select !== null) {
218
overlay_pockets.push(select.get('value'));
221
overlay_pockets.push(null);
225
return overlay_pockets;
228
overlay_components: {
230
var overlay_components = [];
231
this.fieldNode.all("tbody > tr.parent").each(
233
var select = node.one('td.component').one('select');
234
if (select !== null) {
235
overlay_components.push(select.get('value'));
238
overlay_components.push(null);
242
return overlay_components;
248
Y.extend(ParentSeriesListWidget, FormRowWidget, {
250
initializer: function() {
251
ParentSeriesListWidget.superclass.initializer();
252
this.client = new Y.lp.client.Launchpad();
253
this.clean_display();
257
* Display a simple message when no parent series are selected.
259
* @method clean_display
261
clean_display: function() {
262
this.fieldNode.empty();
263
this.fieldNode.append(Y.Node.create("<div />")
264
.set('text', '[No parent for this series yet!]'));
268
* Display the table header.
270
* @method init_display
272
init_display: function() {
273
this.fieldNode.empty();
274
this.fieldNode = Y.Node.create("<table />");
275
var table_header = Y.Node.create(
278
"<th>Parent name</th>",
280
"<th>Component</th>",
287
this.fieldNode.append(table_header);
291
* Helper method to create a select widget from a list and add it
294
* @method build_selector
296
build_selector: function(node, res_list, class_name) {
297
var select = Y.Node.create('<select disabled="disabled"/>');
300
select.appendChild('<option />')
302
.set('value', choice);
304
node.one('td.'+class_name).append(select);
305
node.one('input').on('click', function(e) {
306
var select = node.one('td.'+class_name).one('select');
307
if (select.hasAttribute('disabled')) {
308
select.removeAttribute('disabled');
311
select.setAttribute('disabled', 'disabled');
317
* Build a select widget from a list retrieved from the api.
319
* @method build_select
321
build_select: function(node, class_name, path) {
324
success: function(res_list) {
325
self.build_selector(node, res_list, class_name);
327
failure: function() {
328
var failed_node = Y.Node.create('<span />')
329
.set('text', 'Failed to retrieve content.');
330
node.one('td.'+class_name).append(failed_node);
331
self.disable_overlay(node);
334
this.client.get(path, {on: on});
339
* Move down a parent's line in the table.
343
move_down: function(parent_id) {
344
var node = this.fieldNode.one("tr#parent-" + parent_id);
345
var other = node.next('tr.parent');
346
if (other !== null) { node.swap(other);}
347
Y.lazr.anim.green_flash({node: node}).run();
351
* Move up a parent's line in the table.
355
move_up: function(parent_id) {
356
var node = this.fieldNode.one("tr#parent-" + parent_id);
357
var other = node.previous('tr.parent');
358
if (other !== null) { node.swap(other);}
359
Y.lazr.anim.green_flash({node: node}).run();
363
* Remove a parent series.
365
* @method remove_parent
367
remove_parent: function(parent_id) {
368
if (this.get('parents').length === 1) {
369
this.clean_display();
373
this.fieldNode.one('tr#parent-' + parent_id).remove();
375
Y.fire("parent_removed", parent_id);
379
* Disable the overlay (used when something goes wrong fetching possible
380
* components or pockets for the overlay).
382
* @method disable_overlay
384
disable_overlay: function(parent_node) {
385
var overlay = parent_node.one('input.overlay');
386
if (overlay.get('disabled') !== 'disabled') {
387
Y.lazr.anim.red_flash({node: parent_node}).run();
388
parent_node.one('input.overlay').set('disabled','disabled');
393
* Add a parent series.
397
add_parent: function(parent) {
398
if (this.get('parents').length === 0) {
402
var item = this.fieldNode.one('tr#parent-' + parent.value);
404
Y.lazr.anim.red_flash({node: item}).run();
407
item = Y.Node.create("<tr />")
409
.set('id', 'parent-' + parent.value)
410
.append(Y.Node.create("<td />")
411
.append(Y.Node.create('<a href="" title="Move parent up"/>')
413
.set('innerHTML', '↑'))
414
.append(Y.Node.create('<a href="" title="Move parent down"/>')
415
.addClass('move-down')
416
.set('innerHTML', '↓')))
417
.append(Y.Node.create("<td />")
419
.set('text', parent.title))
420
.append(Y.Node.create("<td />")
421
.set('align', 'center')
422
.append(Y.Node.create('<input type="checkbox" />')
423
.addClass('overlay')))
424
.append(Y.Node.create("<td />")
425
.addClass('component'))
426
.append(Y.Node.create("<td />")
428
.append(Y.Node.create("<td />")
429
.set('align', 'center')
430
.append(Y.Node.create('<span />')
432
.addClass('remove')));
433
this.fieldNode.one('tbody').append(item);
434
this.build_select(item, 'component',
435
parent.api_uri + '/component_names');
436
this.build_select(item, 'pocket',
437
parent.api_uri + '/suite_names');
438
item.one('.move-up').on('click', function(e) {
439
this.move_up(parent.value);
443
item.one('.move-down').on('click', function(e) {
444
this.move_down(parent.value);
448
item.one('.remove').on('click', function(e) {
449
var parent_id = item.get('id').replace('parent-','');
450
this.remove_parent(parent_id);
455
Y.lazr.anim.green_flash({node: item}).run();
460
namespace.ParentSeriesListWidget = ParentSeriesListWidget;
464
* A form row matching that which LaunchpadForm presents, containing a
465
* list of checkboxes, and an optional label and description.
467
* @class ChoiceListWidget
469
var ChoiceListWidget = function() {
470
ChoiceListWidget.superclass.constructor.apply(this, arguments);
473
Y.mix(ChoiceListWidget, {
475
NAME: 'choiceListWidget',
480
* An array of strings from which to choose.
486
return this.fieldNode.all("li > input").get("value");
488
setter: function(value, name) {
489
var choices = Y.Array.unique(value).sort();
490
var list = Y.Node.create("<ul />");
494
var item = self._createChoice(choice);
498
this.fieldNode.empty().append(list);
503
* The current selection.
508
setter: function(value, name) {
509
if (!Y.Lang.isArray(value)) {
512
this.fieldNode.all("li > input").each(
516
value.indexOf(node.get("value")) >= 0);
522
this.fieldNode.all("li > input").each(
524
if (node.get("checked")) {
525
choice.push(node.get("value"));
529
if (this.get("type") === "radio") {
530
if (choice.length === 0) {
533
else if (choice.length === 1) {
545
* The input type to display. Choose from "checkbox" or "radio".
551
setter: function(value, name) {
552
this.fieldNode.all("li > input").set("type", value);
560
Y.extend(ChoiceListWidget, FormRowWidget, {
563
* Helper method to create an entry for the select widget.
565
* @method _createChoice
567
_createChoice: function(choice) {
568
var field_name = this.get("name");
569
var field_type = this.get("type");
570
var item = Y.Node.create(
571
"<li><input /> <label /></li>");
573
.set("type", field_type)
574
.set("name", field_name)
575
.set("value", choice);
578
"for", item.one("input").generateID())
579
.setStyle("font-weight", "normal")
580
.set("text", choice);
585
* Remove a list of choices from the possible widget's choices.
587
* @method remove_choices
589
remove_choices: function(choices) {
592
this.fieldNode.all("select > option").each(
593
function(option) { options.push(option); });
595
"li input[value=" + choice + "]").each(
597
li_input.get('parentNode').remove();
603
Y.lazr.anim.green_flash({node: this.fieldNode}).run();
606
_sorted_position: function(choice) {
608
this.fieldNode.all("input").each(
610
options.push(node.get('value'));
613
options.push(choice);
614
return options.sort().indexOf(choice);
618
* Add new choices (if they are not already present).
620
* @method add_choices
622
add_choices: function(new_choices) {
625
if (this.fieldNode.all(
626
"li > input[value=" + choice + "]").isEmpty()) {
627
var list = this.fieldNode.one('ul');
629
list = Y.Node.create("<ul />");
630
this.fieldNode.empty().append(list);
632
var option = this._createChoice(choice);
633
var options = list.all('input');
634
if (options.isEmpty()) {
638
var pos = this._sorted_position(choice);
640
list.prepend(option);
643
list.insertBefore(option, options.item(pos));
649
Y.lazr.anim.green_flash({node: this.fieldNode}).run();
655
namespace.ChoiceListWidget = ChoiceListWidget;
659
* A special form of ChoiceListWidget for choosing architecture tags.
661
* @class ArchitecturesChoiceListWidget
663
var ArchitecturesChoiceListWidget = function() {
664
ArchitecturesChoiceListWidget
665
.superclass.constructor.apply(this, arguments);
668
Y.mix(ArchitecturesChoiceListWidget, {
670
NAME: 'architecturesChoiceListWidget',
677
Y.extend(ArchitecturesChoiceListWidget, ChoiceListWidget, {
679
initializer: function(config) {
680
this.client = new Y.lp.client.Launchpad();
681
this.error_handler = new Y.lp.client.ErrorHandler();
682
this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
683
this.error_handler.showError = Y.bind(this.showError, this);
684
this._distroseries = {};
685
this.clean_display();
689
* Display a simple message when no parent series are selected.
691
* @method clean_display
693
clean_display: function() {
694
this.fieldNode.empty();
695
this.fieldNode.append(Y.Node.create("<div />")
696
.set('text', '[No architectures to select from yet!]'));
701
* Add a parent distroseries, add the architectures for this new
702
* distroseries to the possible choices.
704
* @method add_distroseries
705
* @param {Object} The distroseries to add ({value:distroseries_id),
706
* api_uri:distroseries_uri}).
708
add_distroseries: function(distroseries) {
709
var path = distroseries.api_uri + "/architectures";
710
var distroseries_id = distroseries.value;
713
success: function (results) {
714
self.add_distroarchseries(distroseries_id, results);
716
failure: this.error_handler.getFailureHandler()
718
this.client.get(path, {on: on});
722
* Remove a parent distroseries, remove the architectures only
723
* present in this parent series from the possible choices.
725
* @method remove_distroseries
727
remove_distroseries: function(distroseries_id) {
728
// Compute which das is only in the distroseries to be removed.
730
var das = this._distroseries[distroseries_id];
732
for (i=0; i<das.entries.length; i++) {
734
arch = das.entries[i].get('architecture_tag');
735
for (ds in this._distroseries) {
736
if (ds !== distroseries_id) {
737
var other_das = this._distroseries[ds];
738
for (j=0; j<other_das.entries.length; j++) {
739
var other_arch = other_das.entries[j].get(
741
if (other_arch === arch) {
748
arch_to_remove.push(arch);
751
delete this._distroseries[distroseries_id];
752
this.remove_choices(arch_to_remove);
753
if (this.fieldNode.all('input').isEmpty()) {
754
this.clean_display();
759
* Add a list of distroarchseries.
761
* @method add_distroarchseries
762
* @param {String} distroseries_id The distroarchseries id.
763
* @param {Object} distroarchseries The distroarchseries object.
765
add_distroarchseries: function(distroseries_id, distroarchseries) {
766
this._distroseries[distroseries_id] = distroarchseries;
767
var choices = distroarchseries.entries.map(
769
return das.get("architecture_tag");
772
this.add_choices(choices);
777
namespace.ArchitecturesChoiceListWidget = ArchitecturesChoiceListWidget;
781
* A special form of FormRowWidget, containing a select control.
783
* @class SelectWidget
785
var SelectWidget = function() {
786
SelectWidget.superclass.constructor.apply(this, arguments);
789
Y.mix(SelectWidget, {
791
NAME: 'selectWidget',
796
* An array of objects from which to choose. Each object
797
* should contain a value for "value", "text" and "data".
803
/* I think this is a YUI3 wart; I can't see any way to
804
map() over a NodeList, so I must push the elements
805
one by one into an array first. */
806
var options = Y.Array([]);
807
this.fieldNode.all("select > option").each(
808
function(option) { options.push(option); });
812
value: option.get("value"),
813
text: option.get("text"),
814
data: option.getData("data")
819
setter: function(value, name) {
820
var select = Y.Node.create("<select />");
821
select.set("name", this.get("name"))
822
.set("size", this.get("size"));
823
if (this.get("multiple")) {
824
select.set("multiple", "multiple");
826
var choices = Y.Array(value);
829
var option = Y.Node.create("<option />");
830
option.set("value", choice.value)
831
.set("text", choice.text)
832
.setData("data", choice.data);
833
select.append(option);
836
if (choices.length > 0) {
837
this.fieldNode.empty().append(select);
840
this.fieldNode.empty();
846
* The current selection.
851
setter: function(value, name) {
852
if (!Y.Lang.isArray(value)) {
855
this.fieldNode.all("select > option").each(
859
value.indexOf(node.get("value")) >= 0);
865
this.fieldNode.all("select > option").each(
867
if (node.get("selected")) {
868
choice.push(node.get("value"));
877
* The number of rows to show in the select widget.
883
setter: function(value, name) {
884
this.fieldNode.all("select").set("size", value);
889
* Whether multiple rows can be selected.
895
setter: function(value, name) {
896
value = value ? true : false;
897
this.fieldNode.all("select").set("multiple", value);
906
Y.extend(SelectWidget, FormRowWidget, {
908
_sorted_position: function(choice) {
910
this.fieldNode.all("option").each(
912
options.push(node.get('text'));
915
options.push(choice);
916
return options.sort().indexOf(choice);
920
* Choose a size for the select control based on the number of
921
* choices, up to an optional maximum size.
925
autoSize: function(maxSize) {
926
var choiceCount = this.fieldNode.all("select > option").size();
927
if (choiceCount === 0) {
930
else if (maxSize === undefined) {
931
this.set("size", choiceCount);
933
else if (choiceCount < maxSize) {
934
this.set("size", choiceCount);
937
this.set("size", maxSize);
944
namespace.SelectWidget = SelectWidget;
948
* A special form of SelectWidget for choosing packagesets.
950
* @class PackagesetPickerWidget
952
var PackagesetPickerWidget = function() {
953
PackagesetPickerWidget
954
.superclass.constructor.apply(this, arguments);
957
Y.mix(PackagesetPickerWidget, {
959
NAME: 'packagesetPickerWidget',
964
* The DistroSeries the choices in this field should
965
* reflect. Takes the form of a string, e.g. "ubuntu/hoary".
967
* @property distroSeries
970
setter: function(value, name) {
971
var distro_series_uri = Y.lp.client.get_absolute_uri(value);
973
start: Y.bind(this.showSpinner, this),
974
success: Y.bind(this.set, this, "packageSets"),
975
failure: this.error_handler.getFailureHandler(),
976
end: Y.bind(this.hideSpinner, this)
981
distroseries: distro_series_uri
984
this.client.named_get("package-sets", "getBySeries", config);
991
Y.extend(PackagesetPickerWidget, SelectWidget, {
994
* Add a distroseries: add its packagesets to the packageset picker.
996
* @method add_distroseries
997
* @param {Object} distroseries The distroseries object.
999
add_distroseries: function(distroseries) {
1000
var distro_series_uri = Y.lp.client.get_absolute_uri(
1001
distroseries.api_uri);
1004
success: function (results) {
1005
self.add_packagesets(results, distroseries);
1007
failure: this.error_handler.getFailureHandler()
1012
distroseries: distro_series_uri
1015
this.client.named_get("package-sets", "getBySeries", config);
1019
* Display a simple message when no parent series are selected.
1021
* @method clean_display
1023
clean_display: function() {
1024
this.fieldNode.empty();
1025
this.fieldNode.append(Y.Node.create("<div />")
1026
.set('text', '[No package sets to select from yet!]'));
1031
* Initialize the picker's select node.
1033
* @method init_select
1035
init_select: function() {
1036
var select = this.fieldNode.one('select');
1037
if (select === null) {
1038
select = Y.Node.create("<select />");
1039
select.set("name", this.get("name"))
1040
.set("size", this.get("size"));
1041
if (this.get("multiple")) {
1042
select.set("multiple", "multiple");
1044
this.fieldNode.empty().append(select);
1050
* Add a choice to the picker.
1052
* @method add_choice
1053
* @param {Object} choice The choice object to be added
1054
* ({text: choice_text, value: choice_value, data: choice_data}).
1056
add_choice: function(choice) {
1057
var select = this.init_select();
1058
var option = Y.Node.create("<option />");
1059
option.set("value", choice.value)
1060
.set("text", choice.text)
1061
.setData("data", choice.data);
1062
var options = select.all('option');
1063
if (options.isEmpty()) {
1064
select.append(option);
1067
var pos = this._sorted_position(choice.text);
1069
select.prepend(option);
1072
select.insertBefore(option, options.item(pos));
1078
* Add choices (a set of packagesets) to the picker.
1080
* @method add_packagesets
1081
* @param {Y.lp.client.Collection} packagesets The collection of
1082
* packagesets to add.
1083
* @param {Object} distroseries The distroseries object
1084
* ({value:distroseries_id), api_uri:distroseries_uri}).
1086
add_packagesets: function(packagesets, distroseries) {
1087
this._packagesets[distroseries.value] = packagesets.entries;
1088
packagesets.entries.forEach(
1089
function(packageset) {
1090
var value = packageset.get("id");
1095
packageset.get("name") + ": " +
1096
packageset.get("description") +
1097
" (" + distroseries.title + ") ")
1101
Y.lazr.anim.green_flash({node: this.fieldNode}).run();
1105
* Remove a distroseries: remove its packagesets from the picker.
1107
* @method remove_distroseries
1108
* @param {String} distroseries_id The id of the distroseries to be
1111
remove_distroseries: function(distroseries_id) {
1112
this._packagesets[distroseries_id].forEach(
1113
function(packageset) {
1115
'option[value="' + packageset.get("id") + '"]').remove();
1117
Y.lazr.anim.green_flash({node: this.fieldNode}).run();
1118
if (this.fieldNode.all('option').isEmpty()) {
1119
this.clean_display();
1123
initializer: function(config) {
1124
this.client = new Y.lp.client.Launchpad();
1125
this.error_handler = new Y.lp.client.ErrorHandler();
1126
this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
1127
this.error_handler.showError = Y.bind(this.showError, this);
1128
this.clean_display();
1129
// _packagesets maps each distroseries' id to a collection of ids
1130
// of its packagesets.
1131
// It's populated each time a new distroseries is added as a parent
1132
// and used when a distroseries is removed to get all the
1133
// corresponding packagesets to be removed from the widget.
1134
this._packagesets = {};
1139
namespace.PackagesetPickerWidget = PackagesetPickerWidget;
1143
* A widget to encapsulate functionality around the form actions.
1145
* @class FormActionsWidget
1147
var FormActionsWidget = function() {
1149
.superclass.constructor.apply(this, arguments);
1152
FormActionsWidget.ATTRS = {
1167
Y.mix(FormActionsWidget, {
1169
NAME: 'formActionsWidget',
1172
submitButtonNode: "input[type=submit]"
1177
Y.extend(FormActionsWidget, Y.Widget, {
1179
initializer: function(config) {
1180
this.client = new Y.lp.client.Launchpad();
1181
this.error_handler = new Y.lp.client.ErrorHandler();
1182
this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
1183
this.error_handler.showError = Y.bind(this.showError, this);
1184
this.submitButtonNode = config.submitButtonNode;
1185
this.spinnerNode = Y.Node.create(
1186
'<img src="/@@/spinner" alt="Loading..." />');
1190
* Show the spinner, and hide the submit button.
1192
* @method showSpinner
1194
showSpinner: function() {
1195
this.submitButtonNode.replace(this.spinnerNode);
1199
* Hide the spinner, and show the submit button again.
1201
* @method hideSpinner
1203
hideSpinner: function() {
1204
this.spinnerNode.replace(this.submitButtonNode);
1212
showError: function(error) {
1213
Y.Node.create('<p class="error message" />')
1214
.appendTo(this.get("contentBox"))
1215
.set("text", error);
1219
* Remove all errors that have been previously displayed by showError.
1221
* @method hideErrors
1223
hideErrors: function(error) {
1224
this.get("contentBox").all("p.error.message").remove();
1229
namespace.FormActionsWidget = FormActionsWidget;
17
var widgets = Y.lp.registry.distroseries.widgets;
18
var formwidgets = Y.lp.app.formwidgets;