Merge lp:~allenap/launchpad/dd-initseries-bug-727105-copy-options into lp:launchpad

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 12705
Proposed branch: lp:~allenap/launchpad/dd-initseries-bug-727105-copy-options
Merge into: lp:launchpad
Prerequisite: lp:~allenap/launchpad/dd-initseries-bug-727105-packageset-picker
Diff against target: 577 lines (+340/-47)
2 files modified
lib/lp/registry/javascript/distroseries.js (+151/-32)
lib/lp/registry/javascript/tests/test_distroseries.js (+189/-15)
To merge this branch: bzr merge lp:~allenap/launchpad/dd-initseries-bug-727105-copy-options
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+54667@code.launchpad.net

Description of the change

- Changed CheckBoxListWidget so that it can show radio-buttons
  too. Renamed it to ChoiceListWidget.

- Add "choice" properties to ChoiceListWidget and SelectWidget to both
  get and set the currently selected option/options.

- Add a SelectWidget.autoSize() method to choose a size for the select
  control based on the number of choices, up to an optional maximum
  size.

- After the packageSets field on PackagesetPickerWidget is set,
  autoSize() is called.

- Adds a "copy options" widget to the page that uses ChoiceListWidget
  in its radio-button mode.

To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/javascript/distroseries.js'
2--- lib/lp/registry/javascript/distroseries.js 2011-03-30 16:16:02 +0000
3+++ lib/lp/registry/javascript/distroseries.js 2011-03-30 16:16:07 +0000
4@@ -133,15 +133,15 @@
5 * A form row matching that which LaunchpadForm presents, containing a
6 * list of checkboxes, and an optional label and description.
7 *
8- * @class CheckBoxListWidget
9+ * @class ChoiceListWidget
10 */
11-var CheckBoxListWidget = function() {
12- CheckBoxListWidget.superclass.constructor.apply(this, arguments);
13+var ChoiceListWidget = function() {
14+ ChoiceListWidget.superclass.constructor.apply(this, arguments);
15 };
16
17-Y.mix(CheckBoxListWidget, {
18+Y.mix(ChoiceListWidget, {
19
20- NAME: 'checkBoxListWidget',
21+ NAME: 'choiceListWidget',
22
23 ATTRS: {
24
25@@ -157,13 +157,14 @@
26 setter: function(value, name) {
27 var choices = Y.Array.unique(value).sort();
28 var field_name = this.get("name");
29+ var field_type = this.get("type");
30 var list = Y.Node.create("<ul />");
31 choices.forEach(
32 function(choice) {
33 var item = Y.Node.create(
34 "<li><input /> <label /></li>");
35 item.one("input")
36- .set("type", "checkbox")
37+ .set("type", field_type)
38 .set("name", field_name)
39 .set("value", choice);
40 item.one("label")
41@@ -176,30 +177,84 @@
42 );
43 this.fieldNode.empty().append(list);
44 }
45+ },
46+
47+ /**
48+ * The current selection.
49+ *
50+ * @property choice
51+ */
52+ choice: {
53+ setter: function(value, name) {
54+ if (!Y.Lang.isArray(value)) {
55+ value = [value];
56+ }
57+ this.fieldNode.all("li > input").each(
58+ function(node) {
59+ node.set(
60+ "checked",
61+ value.indexOf(node.get("value")) >= 0);
62+ }
63+ );
64+ },
65+ getter: function() {
66+ var choice = [];
67+ this.fieldNode.all("li > input").each(
68+ function(node) {
69+ if (node.get("checked")) {
70+ choice.push(node.get("value"));
71+ }
72+ }
73+ );
74+ if (this.get("type") == "radio") {
75+ if (choice.length === 0) {
76+ choice = null;
77+ }
78+ else if (choice.length == 1) {
79+ choice = choice[0];
80+ }
81+ else {
82+ choice = undefined;
83+ }
84+ }
85+ return choice;
86+ }
87+ },
88+
89+ /**
90+ * The input type to display. Choose from "checkbox" or "radio".
91+ *
92+ * @property type
93+ */
94+ type: {
95+ value: "checkbox",
96+ setter: function(value, name) {
97+ this.fieldNode.all("li > input").set("type", value);
98+ }
99 }
100
101 }
102
103 });
104
105-Y.extend(CheckBoxListWidget, FormRowWidget);
106+Y.extend(ChoiceListWidget, FormRowWidget);
107
108-namespace.CheckBoxListWidget = CheckBoxListWidget;
109+namespace.ChoiceListWidget = ChoiceListWidget;
110
111
112 /**
113- * A special form of CheckBoxListWidget for choosing architecture tags.
114+ * A special form of ChoiceListWidget for choosing architecture tags.
115 *
116- * @class ArchitecturesCheckBoxListWidget
117+ * @class ArchitecturesChoiceListWidget
118 */
119-var ArchitecturesCheckBoxListWidget = function() {
120- ArchitecturesCheckBoxListWidget
121+var ArchitecturesChoiceListWidget = function() {
122+ ArchitecturesChoiceListWidget
123 .superclass.constructor.apply(this, arguments);
124 };
125
126-Y.mix(ArchitecturesCheckBoxListWidget, {
127+Y.mix(ArchitecturesChoiceListWidget, {
128
129- NAME: 'architecturesCheckBoxListWidget',
130+ NAME: 'architecturesChoiceListWidget',
131
132 ATTRS: {
133
134@@ -250,7 +305,7 @@
135
136 });
137
138-Y.extend(ArchitecturesCheckBoxListWidget, CheckBoxListWidget, {
139+Y.extend(ArchitecturesChoiceListWidget, ChoiceListWidget, {
140
141 initializer: function(config) {
142 this.client = new Y.lp.client.Launchpad();
143@@ -261,7 +316,7 @@
144
145 });
146
147-namespace.ArchitecturesCheckBoxListWidget = ArchitecturesCheckBoxListWidget;
148+namespace.ArchitecturesChoiceListWidget = ArchitecturesChoiceListWidget;
149
150
151 /**
152@@ -330,6 +385,37 @@
153 },
154
155 /**
156+ * The current selection.
157+ *
158+ * @property choice
159+ */
160+ choice: {
161+ setter: function(value, name) {
162+ if (!Y.Lang.isArray(value)) {
163+ value = [value];
164+ }
165+ this.fieldNode.all("select > option").each(
166+ function(node) {
167+ node.set(
168+ "selected",
169+ value.indexOf(node.get("value")) >= 0);
170+ }
171+ );
172+ },
173+ getter: function() {
174+ var choice = [];
175+ this.fieldNode.all("select > option").each(
176+ function(node) {
177+ if (node.get("selected")) {
178+ choice.push(node.get("value"));
179+ }
180+ }
181+ );
182+ return choice;
183+ }
184+ },
185+
186+ /**
187 * The number of rows to show in the select widget.
188 *
189 * @property size
190@@ -349,15 +435,9 @@
191 multiple: {
192 value: false,
193 setter: function(value, name) {
194- var select = this.fieldNode.all("select");
195- if (value) {
196- select.setAttribute("multiple", "multiple");
197- return true;
198- }
199- else {
200- select.removeAttribute("multiple");
201- return false;
202- }
203+ value = value ? true : false;
204+ this.fieldNode.all("select").set("multiple", value);
205+ return value;
206 }
207 }
208
209@@ -365,7 +445,32 @@
210
211 });
212
213-Y.extend(SelectWidget, FormRowWidget);
214+Y.extend(SelectWidget, FormRowWidget, {
215+
216+ /**
217+ * Choose a size for the select control based on the number of
218+ * choices, up to an optional maximum size.
219+ *
220+ * @method autoSize
221+ */
222+ autoSize: function(maxSize) {
223+ var choiceCount = this.fieldNode.all("select > option").size();
224+ if (choiceCount === 0) {
225+ this.set("size", 1);
226+ }
227+ else if (maxSize === undefined) {
228+ this.set("size", choiceCount);
229+ }
230+ else if (choiceCount < maxSize) {
231+ this.set("size", choiceCount);
232+ }
233+ else {
234+ this.set("size", maxSize);
235+ }
236+ return this;
237+ }
238+
239+});
240
241 namespace.SelectWidget = SelectWidget;
242
243@@ -438,6 +543,9 @@
244 "text",
245 "The chosen series has no package sets!"));
246 }
247+ else {
248+ this.autoSize(10);
249+ }
250 Y.lazr.anim.green_flash({node: this.fieldNode}).run();
251 }
252 }
253@@ -468,7 +576,7 @@
254 namespace.setup = function() {
255 var form_container = Y.one("#init-series-form-container");
256 var form_table_body = form_container.one("table.form > tbody");
257- var architecture_choice = new ArchitecturesCheckBoxListWidget()
258+ var architecture_choice = new ArchitecturesChoiceListWidget()
259 .set("name", "field.architectures")
260 .set("label", "Architectures")
261 .set("description", (
262@@ -484,15 +592,26 @@
263 "The package sets that will be imported " +
264 "into the derived distroseries."))
265 .render(form_table_body);
266- var field_derived_from_series =
267+ var package_copy_options = new ChoiceListWidget()
268+ .set("name", "field.package_copy_options")
269+ .set("type", "radio")
270+ .set("label", "Copy options")
271+ .set("description", (
272+ "Choose whether to rebuild all the sources you copy " +
273+ "from the parent, or to copy their binaries too."))
274+ .set("choices", ["Copy Source and Rebuild",
275+ "Copy Source and Binaries"])
276+ .set("choice", "Copy Source and Binaries")
277+ .render(form_table_body);
278+ var derive_from_choice =
279 form_table_body.one("[name=field.derived_from_series]");
280
281 // Wire up the distroseries select to the architectures widget.
282 function update_architecture_choice() {
283 architecture_choice
284- .set("distroSeries", field_derived_from_series.get("value"));
285+ .set("distroSeries", derive_from_choice.get("value"));
286 }
287- field_derived_from_series.on("change", update_architecture_choice);
288+ derive_from_choice.on("change", update_architecture_choice);
289
290 // Update the architectures widget for the selected distroseries.
291 update_architecture_choice();
292@@ -500,9 +619,9 @@
293 // Wire up the distroseries select to the packagesets widget.
294 function update_packageset_choice() {
295 packageset_choice
296- .set("distroSeries", field_derived_from_series.get("value"));
297+ .set("distroSeries", derive_from_choice.get("value"));
298 }
299- field_derived_from_series.on("change", update_packageset_choice);
300+ derive_from_choice.on("change", update_packageset_choice);
301
302 // Update the packagesets widget for the selected distroseries.
303 update_packageset_choice();
304
305=== modified file 'lib/lp/registry/javascript/tests/test_distroseries.js'
306--- lib/lp/registry/javascript/tests/test_distroseries.js 2011-03-30 16:16:02 +0000
307+++ lib/lp/registry/javascript/tests/test_distroseries.js 2011-03-30 16:16:07 +0000
308@@ -123,12 +123,12 @@
309
310 suite.add(new Y.Test.Case(testFormRowWidget));
311
312- var testCheckBoxListWidget = {
313- name: 'TestCheckBoxListWidget',
314+ var testChoiceListWidget = {
315+ name: 'TestChoiceListWidget',
316
317 setUp: function() {
318 this.container = Y.Node.create("<div />");
319- this.widget = new initseries.CheckBoxListWidget();
320+ this.widget = new initseries.ChoiceListWidget();
321 },
322
323 tearDown: function() {
324@@ -144,6 +144,9 @@
325 ArrayAssert.itemsAreEqual(
326 ["a", "b"],
327 this.container.all("li > label").get("text"));
328+ ArrayAssert.itemsAreEqual(
329+ ["checkbox", "checkbox"],
330+ this.container.all("li > input").getAttribute("type"));
331 },
332
333 testRenderChoicesChange: function() {
334@@ -156,20 +159,90 @@
335 ArrayAssert.itemsAreEqual(
336 ["c", "d", "e"],
337 this.container.all("li > label").get("text"));
338+ },
339+
340+ testRenderChoicesChangeType: function() {
341+ this.widget.set("choices", ["a", "b"]);
342+ this.widget.render(this.container);
343+ this.widget.set("type", "radio");
344+ ArrayAssert.itemsAreEqual(
345+ ["radio", "radio"],
346+ this.container.all("li > input").getAttribute("type"));
347+
348+ },
349+
350+ testChoiceWithCheckBox: function() {
351+ this.widget
352+ .set("type", "checkbox")
353+ .set("choices", ["a", "b"]);
354+ ArrayAssert.itemsAreEqual(
355+ [], this.widget.get("choice"));
356+ this.widget.fieldNode.one("input[value=a]")
357+ .set("checked", "checked");
358+ ArrayAssert.itemsAreEqual(
359+ ["a"], this.widget.get("choice"));
360+ },
361+
362+ testChoiceWithRadio: function() {
363+ this.widget
364+ .set("type", "radio")
365+ .set("choices", ["a", "b"]);
366+ Assert.isNull(this.widget.get("choice"));
367+ this.widget.fieldNode.one("input[value=a]")
368+ .set("checked", "checked");
369+ Assert.areEqual("a", this.widget.get("choice"));
370+ /* When both radio buttons are checked (should that be
371+ possible?), choice is undefined. */
372+ this.widget.fieldNode.one("input[value=b]")
373+ .set("checked", "checked");
374+ Assert.isUndefined(this.widget.get("choice"));
375+ },
376+
377+ testSetChoiceWithCheckBox: function() {
378+ this.widget
379+ .set("type", "checkbox")
380+ .set("choices", ["a", "b"])
381+ .set("choice", "a");
382+ ArrayAssert.itemsAreEqual(
383+ ["a"], this.widget.get("choice"));
384+ this.widget.set("choice", ["a"]);
385+ ArrayAssert.itemsAreEqual(
386+ ["a"], this.widget.get("choice"));
387+ this.widget.set("choice", ["a", "b"]);
388+ ArrayAssert.itemsAreEqual(
389+ ["a", "b"], this.widget.get("choice"));
390+ this.widget.set("choice", ["b", "c"]);
391+ ArrayAssert.itemsAreEqual(
392+ ["b"], this.widget.get("choice"));
393+ },
394+
395+ testSetChoiceWithRadio: function() {
396+ this.widget
397+ .set("type", "radio")
398+ .set("choices", ["a", "b"])
399+ .set("choice", "a");
400+ ArrayAssert.itemsAreEqual(
401+ "a", this.widget.get("choice"));
402+ this.widget.set("choice", ["a"]);
403+ ArrayAssert.itemsAreEqual(
404+ "a", this.widget.get("choice"));
405+ this.widget.set("choice", "b");
406+ ArrayAssert.itemsAreEqual(
407+ "b", this.widget.get("choice"));
408 }
409
410 };
411
412- testCheckBoxListWidget = Y.merge(
413- testFormRowWidget, testCheckBoxListWidget);
414- suite.add(new Y.Test.Case(testCheckBoxListWidget));
415+ testChoiceListWidget = Y.merge(
416+ testFormRowWidget, testChoiceListWidget);
417+ suite.add(new Y.Test.Case(testChoiceListWidget));
418
419- var testArchitecturesCheckBoxListWidget = {
420- name: 'TestArchitecturesCheckBoxListWidget',
421+ var testArchitecturesChoiceListWidget = {
422+ name: 'TestArchitecturesChoiceListWidget',
423
424 setUp: function() {
425 this.container = Y.Node.create("<div />");
426- this.widget = new initseries.ArchitecturesCheckBoxListWidget();
427+ this.widget = new initseries.ArchitecturesChoiceListWidget();
428 },
429
430 tearDown: function() {
431@@ -262,9 +335,9 @@
432
433 };
434
435- testArchitecturesCheckBoxListWidget = Y.merge(
436- testCheckBoxListWidget, testArchitecturesCheckBoxListWidget);
437- suite.add(new Y.Test.Case(testArchitecturesCheckBoxListWidget));
438+ testArchitecturesChoiceListWidget = Y.merge(
439+ testChoiceListWidget, testArchitecturesChoiceListWidget);
440+ suite.add(new Y.Test.Case(testArchitecturesChoiceListWidget));
441
442 var testSelectWidget = {
443 name: 'TestSelectWidget',
444@@ -358,6 +431,54 @@
445 this.container.all("select > option").get("text"));
446 },
447
448+ testChoice: function() {
449+ var choices = [
450+ {value: "a", text: "A", data: 123},
451+ {value: "b", text: "B", data: 456},
452+ {value: "c", text: "C", data: 789}
453+ ];
454+ this.widget
455+ .set("choices", choices)
456+ .set("multiple", true);
457+ /* It would be better to deselect all options by default,
458+ but this appears impossible; the browser seems to
459+ select the first option when rendering. */
460+ this.widget.fieldNode.all("option").set("selected", false);
461+ ArrayAssert.itemsAreEqual(
462+ [], this.widget.get("choice"));
463+ this.widget.fieldNode.one("option[value=a]")
464+ .set("selected", true);
465+ ArrayAssert.itemsAreEqual(
466+ ["a"], this.widget.get("choice"));
467+ this.widget.fieldNode.one("option[value=c]")
468+ .set("selected", true);
469+ ArrayAssert.itemsAreEqual(
470+ ["a", "c"], this.widget.get("choice"));
471+ },
472+
473+ testSetChoice: function() {
474+ var choices = [
475+ {value: "a", text: "A", data: 123},
476+ {value: "b", text: "B", data: 456},
477+ {value: "c", text: "C", data: 789}
478+ ];
479+ this.widget
480+ .set("multiple", true)
481+ .set("choices", choices)
482+ .set("choice", "a");
483+ ArrayAssert.itemsAreEqual(
484+ ["a"], this.widget.get("choice"));
485+ this.widget.set("choice", ["a"]);
486+ ArrayAssert.itemsAreEqual(
487+ ["a"], this.widget.get("choice"));
488+ this.widget.set("choice", ["a", "b"]);
489+ ArrayAssert.itemsAreEqual(
490+ ["a", "b"], this.widget.get("choice"));
491+ this.widget.set("choice", ["b", "z"]);
492+ ArrayAssert.itemsAreEqual(
493+ ["b"], this.widget.get("choice"));
494+ },
495+
496 testSize: function() {
497 Assert.areEqual(1, this.widget.get("size"));
498 },
499@@ -391,6 +512,46 @@
500 5, this.widget.fieldNode.one("select").get("size"));
501 },
502
503+ testAutoSize: function() {
504+ var choices = [
505+ {value: "a", text: "A", data: 123},
506+ {value: "b", text: "B", data: 456},
507+ {value: "c", text: "C", data: 789}
508+ ];
509+ this.widget.set("choices", choices);
510+ /* Without argument, autoSize() sets the size to the same
511+ as the number of choices. */
512+ this.widget.autoSize();
513+ Assert.areEqual(3, this.widget.get("size"));
514+ },
515+
516+ testAutoSizeMoreChoicesThanMaxiumum: function() {
517+ var choices = [
518+ {value: "a", text: "A", data: 123},
519+ {value: "b", text: "B", data: 456},
520+ {value: "c", text: "C", data: 789}
521+ ];
522+ this.widget.set("choices", choices);
523+ /* autoSize() sets the size to the same as the number of
524+ choices unless there are more than the specified
525+ maximum. */
526+ this.widget.autoSize(2);
527+ Assert.areEqual(2, this.widget.get("size"));
528+ },
529+
530+ testAutoSizeFewerChoicesThanMaxiumum: function() {
531+ var choices = [
532+ {value: "a", text: "A", data: 123},
533+ {value: "b", text: "B", data: 456},
534+ {value: "c", text: "C", data: 789}
535+ ];
536+ this.widget.set("choices", choices);
537+ /* autoSize() sets the size to the same as the number of
538+ choices. */
539+ this.widget.autoSize(5);
540+ Assert.areEqual(3, this.widget.get("size"));
541+ },
542+
543 testMultiple: function() {
544 Assert.areEqual(false, this.widget.get("multiple"));
545 },
546@@ -453,15 +614,28 @@
547 var package_sets_collection =
548 new Y.lp.client.Collection(
549 null, {entries: package_sets}, null);
550- this.widget.set(
551- "packageSets",
552- package_sets_collection);
553+ this.widget.set("packageSets", package_sets_collection);
554 var choices = this.widget.get("choices");
555 ArrayAssert.itemsAreEqual(
556 ["foo", "bar", "baz"],
557 attrselect("value")(choices));
558 },
559
560+ testSetPackageSetsCallsAutoSize: function() {
561+ var package_sets = [
562+ {name: "foo", description: "Foo"},
563+ {name: "bar", description: "Bar"},
564+ {name: "baz", description: "Baz"}
565+ ];
566+ var package_sets_collection =
567+ new Y.lp.client.Collection(
568+ null, {entries: package_sets}, null);
569+ var autoSized = false;
570+ this.widget.autoSize = function() { autoSized = true; };
571+ this.widget.set("packageSets", package_sets_collection);
572+ Assert.isTrue(autoSized);
573+ },
574+
575 testSetDistroSeriesInitiatesIO: function() {
576 var io = false;
577 this.widget.client = {