← Back to team overview

launchpad-reviewers team mailing list archive

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

 

Gavin Panella has proposed merging lp:~allenap/launchpad/dd-initseries-bug-727105-copy-options into lp:launchpad with lp:~allenap/launchpad/dd-initseries-bug-727105-packageset-picker as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #740349 in Launchpad itself: "+initseries: package copy options"
  https://bugs.launchpad.net/launchpad/+bug/740349

For more details, see:
https://code.launchpad.net/~allenap/launchpad/dd-initseries-bug-727105-copy-options/+merge/54667

- 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.

-- 
https://code.launchpad.net/~allenap/launchpad/dd-initseries-bug-727105-copy-options/+merge/54667
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/dd-initseries-bug-727105-copy-options into lp:launchpad.
=== modified file 'lib/lp/registry/javascript/distroseries.js'
--- lib/lp/registry/javascript/distroseries.js	2011-03-24 09:48:33 +0000
+++ lib/lp/registry/javascript/distroseries.js	2011-03-24 09:48:35 +0000
@@ -133,15 +133,15 @@
  * A form row matching that which LaunchpadForm presents, containing a
  * list of checkboxes, and an optional label and description.
  *
- * @class CheckBoxListWidget
+ * @class ChoiceListWidget
  */
-var CheckBoxListWidget = function() {
-    CheckBoxListWidget.superclass.constructor.apply(this, arguments);
+var ChoiceListWidget = function() {
+    ChoiceListWidget.superclass.constructor.apply(this, arguments);
 };
 
-Y.mix(CheckBoxListWidget, {
+Y.mix(ChoiceListWidget, {
 
-    NAME: 'checkBoxListWidget',
+    NAME: 'choiceListWidget',
 
     ATTRS: {
 
@@ -157,13 +157,14 @@
             setter: function(value, name) {
                 var choices = Y.Array.unique(value).sort();
                 var field_name = this.get("name");
+                var field_type = this.get("type");
                 var list = Y.Node.create("<ul />");
                 choices.forEach(
                     function(choice) {
                         var item = Y.Node.create(
                             "<li><input /> <label /></li>");
                         item.one("input")
-                            .set("type", "checkbox")
+                            .set("type", field_type)
                             .set("name", field_name)
                             .set("value", choice);
                         item.one("label")
@@ -176,30 +177,84 @@
                 );
                 this.fieldNode.empty().append(list);
             }
+        },
+
+        /**
+         * The current selection.
+         *
+         * @property choice
+         */
+        choice: {
+            setter: function(value, name) {
+                if (!Y.Lang.isArray(value)) {
+                    value = [value];
+                }
+                this.fieldNode.all("li > input").each(
+                    function(node) {
+                        node.set(
+                            "checked",
+                            value.indexOf(node.get("value")) >= 0);
+                    }
+                );
+            },
+            getter: function() {
+                var choice = [];
+                this.fieldNode.all("li > input").each(
+                    function(node) {
+                        if (node.get("checked")) {
+                            choice.push(node.get("value"));
+                        }
+                    }
+                );
+                if (this.get("type") == "radio") {
+                    if (choice.length === 0) {
+                        choice = null;
+                    }
+                    else if (choice.length == 1) {
+                        choice = choice[0];
+                    }
+                    else {
+                        choice = undefined;
+                    }
+                }
+                return choice;
+            }
+        },
+
+        /**
+         * The input type to display. Choose from "checkbox" or "radio".
+         *
+         * @property type
+         */
+        type: {
+            value: "checkbox",
+            setter: function(value, name) {
+                this.fieldNode.all("li > input").set("type", value);
+            }
         }
 
     }
 
 });
 
-Y.extend(CheckBoxListWidget, FormRowWidget);
+Y.extend(ChoiceListWidget, FormRowWidget);
 
-namespace.CheckBoxListWidget = CheckBoxListWidget;
+namespace.ChoiceListWidget = ChoiceListWidget;
 
 
 /**
- * A special form of CheckBoxListWidget for choosing architecture tags.
+ * A special form of ChoiceListWidget for choosing architecture tags.
  *
- * @class ArchitecturesCheckBoxListWidget
+ * @class ArchitecturesChoiceListWidget
  */
-var ArchitecturesCheckBoxListWidget = function() {
-    ArchitecturesCheckBoxListWidget
+var ArchitecturesChoiceListWidget = function() {
+    ArchitecturesChoiceListWidget
         .superclass.constructor.apply(this, arguments);
 };
 
-Y.mix(ArchitecturesCheckBoxListWidget, {
+Y.mix(ArchitecturesChoiceListWidget, {
 
-    NAME: 'architecturesCheckBoxListWidget',
+    NAME: 'architecturesChoiceListWidget',
 
     ATTRS: {
 
@@ -250,7 +305,7 @@
 
 });
 
-Y.extend(ArchitecturesCheckBoxListWidget, CheckBoxListWidget, {
+Y.extend(ArchitecturesChoiceListWidget, ChoiceListWidget, {
 
     initializer: function(config) {
         this.client = new Y.lp.client.Launchpad();
@@ -261,7 +316,7 @@
 
 });
 
-namespace.ArchitecturesCheckBoxListWidget = ArchitecturesCheckBoxListWidget;
+namespace.ArchitecturesChoiceListWidget = ArchitecturesChoiceListWidget;
 
 
 /**
@@ -330,6 +385,37 @@
         },
 
         /**
+         * The current selection.
+         *
+         * @property choice
+         */
+        choice: {
+            setter: function(value, name) {
+                if (!Y.Lang.isArray(value)) {
+                    value = [value];
+                }
+                this.fieldNode.all("select > option").each(
+                    function(node) {
+                        node.set(
+                            "selected",
+                            value.indexOf(node.get("value")) >= 0);
+                    }
+                );
+            },
+            getter: function() {
+                var choice = [];
+                this.fieldNode.all("select > option").each(
+                    function(node) {
+                        if (node.get("selected")) {
+                            choice.push(node.get("value"));
+                        }
+                    }
+                );
+                return choice;
+            }
+        },
+
+        /**
          * The number of rows to show in the select widget.
          *
          * @property size
@@ -349,15 +435,9 @@
         multiple: {
             value: false,
             setter: function(value, name) {
-                var select = this.fieldNode.all("select");
-                if (value) {
-                    select.setAttribute("multiple", "multiple");
-                    return true;
-                }
-                else {
-                    select.removeAttribute("multiple");
-                    return false;
-                }
+                value = value ? true : false;
+                this.fieldNode.all("select").set("multiple", value);
+                return value;
             }
         }
 
@@ -365,7 +445,32 @@
 
 });
 
-Y.extend(SelectWidget, FormRowWidget);
+Y.extend(SelectWidget, FormRowWidget, {
+
+    /**
+     * Choose a size for the select control based on the number of
+     * choices, up to an optional maximum size.
+     *
+     * @method autoSize
+     */
+    autoSize: function(maxSize) {
+        var choiceCount = this.fieldNode.all("select > option").size();
+        if (choiceCount === 0) {
+            this.set("size", 1);
+        }
+        else if (maxSize === undefined) {
+            this.set("size", choiceCount);
+        }
+        else if (choiceCount < maxSize) {
+            this.set("size", choiceCount);
+        }
+        else {
+            this.set("size", maxSize);
+        }
+        return this;
+    }
+
+});
 
 namespace.SelectWidget = SelectWidget;
 
@@ -438,6 +543,9 @@
                             "text",
                             "The chosen series has no package sets!"));
                 }
+                else {
+                    this.autoSize(10);
+                }
                 Y.lazr.anim.green_flash({node: this.fieldNode}).run();
             }
         }
@@ -468,7 +576,7 @@
 namespace.setup = function() {
     var form_container = Y.one("#init-series-form-container");
     var form_table_body = form_container.one("table.form > tbody");
-    var architecture_choice = new ArchitecturesCheckBoxListWidget()
+    var architecture_choice = new ArchitecturesChoiceListWidget()
         .set("name", "field.architectures")
         .set("label", "Architectures")
         .set("description", (
@@ -484,15 +592,26 @@
                  "The package sets that will be imported " +
                  "into the derived distroseries."))
         .render(form_table_body);
-    var field_derived_from_series =
+    var package_copy_options = new ChoiceListWidget()
+        .set("name", "field.package_copy_options")
+        .set("type", "radio")
+        .set("label", "Copy options")
+        .set("description", (
+                 "Choose whether to rebuild all the sources you copy " +
+                 "from the parent, or to copy their binaries too."))
+        .set("choices", ["Copy Source and Rebuild",
+                         "Copy Source and Binaries"])
+        .set("choice", "Copy Source and Binaries")
+        .render(form_table_body);
+    var derive_from_choice =
         form_table_body.one("[name=field.derived_from_series]");
 
     // Wire up the distroseries select to the architectures widget.
     function update_architecture_choice() {
         architecture_choice
-            .set("distroSeries", field_derived_from_series.get("value"));
+            .set("distroSeries", derive_from_choice.get("value"));
     }
-    field_derived_from_series.on("change", update_architecture_choice);
+    derive_from_choice.on("change", update_architecture_choice);
 
     // Update the architectures widget for the selected distroseries.
     update_architecture_choice();
@@ -500,9 +619,9 @@
     // Wire up the distroseries select to the packagesets widget.
     function update_packageset_choice() {
         packageset_choice
-            .set("distroSeries", field_derived_from_series.get("value"));
+            .set("distroSeries", derive_from_choice.get("value"));
     }
-    field_derived_from_series.on("change", update_packageset_choice);
+    derive_from_choice.on("change", update_packageset_choice);
 
     // Update the packagesets widget for the selected distroseries.
     update_packageset_choice();

=== modified file 'lib/lp/registry/javascript/tests/test_distroseries.js'
--- lib/lp/registry/javascript/tests/test_distroseries.js	2011-03-24 09:48:33 +0000
+++ lib/lp/registry/javascript/tests/test_distroseries.js	2011-03-24 09:48:35 +0000
@@ -123,12 +123,12 @@
 
     suite.add(new Y.Test.Case(testFormRowWidget));
 
-    var testCheckBoxListWidget = {
-        name: 'TestCheckBoxListWidget',
+    var testChoiceListWidget = {
+        name: 'TestChoiceListWidget',
 
         setUp: function() {
             this.container = Y.Node.create("<div />");
-            this.widget = new initseries.CheckBoxListWidget();
+            this.widget = new initseries.ChoiceListWidget();
         },
 
         tearDown: function() {
@@ -144,6 +144,9 @@
             ArrayAssert.itemsAreEqual(
                 ["a", "b"],
                 this.container.all("li > label").get("text"));
+            ArrayAssert.itemsAreEqual(
+                ["checkbox", "checkbox"],
+                this.container.all("li > input").getAttribute("type"));
         },
 
         testRenderChoicesChange: function() {
@@ -156,20 +159,90 @@
             ArrayAssert.itemsAreEqual(
                 ["c", "d", "e"],
                 this.container.all("li > label").get("text"));
+        },
+
+        testRenderChoicesChangeType: function() {
+            this.widget.set("choices", ["a", "b"]);
+            this.widget.render(this.container);
+            this.widget.set("type", "radio");
+            ArrayAssert.itemsAreEqual(
+                ["radio", "radio"],
+                this.container.all("li > input").getAttribute("type"));
+
+        },
+
+        testChoiceWithCheckBox: function() {
+            this.widget
+                .set("type", "checkbox")
+                .set("choices", ["a", "b"]);
+            ArrayAssert.itemsAreEqual(
+                [], this.widget.get("choice"));
+            this.widget.fieldNode.one("input[value=a]")
+                .set("checked", "checked");
+            ArrayAssert.itemsAreEqual(
+                ["a"], this.widget.get("choice"));
+        },
+
+        testChoiceWithRadio: function() {
+            this.widget
+                .set("type", "radio")
+                .set("choices", ["a", "b"]);
+            Assert.isNull(this.widget.get("choice"));
+            this.widget.fieldNode.one("input[value=a]")
+                .set("checked", "checked");
+            Assert.areEqual("a", this.widget.get("choice"));
+            /* When both radio buttons are checked (should that be
+               possible?), choice is undefined. */
+            this.widget.fieldNode.one("input[value=b]")
+                .set("checked", "checked");
+            Assert.isUndefined(this.widget.get("choice"));
+        },
+
+        testSetChoiceWithCheckBox: function() {
+            this.widget
+                .set("type", "checkbox")
+                .set("choices", ["a", "b"])
+                .set("choice", "a");
+            ArrayAssert.itemsAreEqual(
+                ["a"], this.widget.get("choice"));
+            this.widget.set("choice", ["a"]);
+            ArrayAssert.itemsAreEqual(
+                ["a"], this.widget.get("choice"));
+            this.widget.set("choice", ["a", "b"]);
+            ArrayAssert.itemsAreEqual(
+                ["a", "b"], this.widget.get("choice"));
+            this.widget.set("choice", ["b", "c"]);
+            ArrayAssert.itemsAreEqual(
+                ["b"], this.widget.get("choice"));
+        },
+
+        testSetChoiceWithRadio: function() {
+            this.widget
+                .set("type", "radio")
+                .set("choices", ["a", "b"])
+                .set("choice", "a");
+            ArrayAssert.itemsAreEqual(
+                "a", this.widget.get("choice"));
+            this.widget.set("choice", ["a"]);
+            ArrayAssert.itemsAreEqual(
+                "a", this.widget.get("choice"));
+            this.widget.set("choice", "b");
+            ArrayAssert.itemsAreEqual(
+                "b", this.widget.get("choice"));
         }
 
     };
 
-    testCheckBoxListWidget = Y.merge(
-        testFormRowWidget, testCheckBoxListWidget);
-    suite.add(new Y.Test.Case(testCheckBoxListWidget));
+    testChoiceListWidget = Y.merge(
+        testFormRowWidget, testChoiceListWidget);
+    suite.add(new Y.Test.Case(testChoiceListWidget));
 
-    var testArchitecturesCheckBoxListWidget = {
-        name: 'TestArchitecturesCheckBoxListWidget',
+    var testArchitecturesChoiceListWidget = {
+        name: 'TestArchitecturesChoiceListWidget',
 
         setUp: function() {
             this.container = Y.Node.create("<div />");
-            this.widget = new initseries.ArchitecturesCheckBoxListWidget();
+            this.widget = new initseries.ArchitecturesChoiceListWidget();
         },
 
         tearDown: function() {
@@ -262,9 +335,9 @@
 
     };
 
-    testArchitecturesCheckBoxListWidget = Y.merge(
-        testCheckBoxListWidget, testArchitecturesCheckBoxListWidget);
-    suite.add(new Y.Test.Case(testArchitecturesCheckBoxListWidget));
+    testArchitecturesChoiceListWidget = Y.merge(
+        testChoiceListWidget, testArchitecturesChoiceListWidget);
+    suite.add(new Y.Test.Case(testArchitecturesChoiceListWidget));
 
     var testSelectWidget = {
         name: 'TestSelectWidget',
@@ -358,6 +431,54 @@
                 this.container.all("select > option").get("text"));
         },
 
+        testChoice: function() {
+            var choices = [
+                {value: "a", text: "A", data: 123},
+                {value: "b", text: "B", data: 456},
+                {value: "c", text: "C", data: 789}
+            ];
+            this.widget
+                .set("choices", choices)
+                .set("multiple", true);
+            /* It would be better to deselect all options by default,
+               but this appears impossible; the browser seems to
+               select the first option when rendering. */
+            this.widget.fieldNode.all("option").set("selected", false);
+            ArrayAssert.itemsAreEqual(
+                [], this.widget.get("choice"));
+            this.widget.fieldNode.one("option[value=a]")
+                .set("selected", true);
+            ArrayAssert.itemsAreEqual(
+                ["a"], this.widget.get("choice"));
+            this.widget.fieldNode.one("option[value=c]")
+                .set("selected", true);
+            ArrayAssert.itemsAreEqual(
+                ["a", "c"], this.widget.get("choice"));
+        },
+
+        testSetChoice: function() {
+            var choices = [
+                {value: "a", text: "A", data: 123},
+                {value: "b", text: "B", data: 456},
+                {value: "c", text: "C", data: 789}
+            ];
+            this.widget
+                .set("multiple", true)
+                .set("choices", choices)
+                .set("choice", "a");
+            ArrayAssert.itemsAreEqual(
+                ["a"], this.widget.get("choice"));
+            this.widget.set("choice", ["a"]);
+            ArrayAssert.itemsAreEqual(
+                ["a"], this.widget.get("choice"));
+            this.widget.set("choice", ["a", "b"]);
+            ArrayAssert.itemsAreEqual(
+                ["a", "b"], this.widget.get("choice"));
+            this.widget.set("choice", ["b", "z"]);
+            ArrayAssert.itemsAreEqual(
+                ["b"], this.widget.get("choice"));
+        },
+
         testSize: function() {
             Assert.areEqual(1, this.widget.get("size"));
         },
@@ -391,6 +512,46 @@
                 5, this.widget.fieldNode.one("select").get("size"));
         },
 
+        testAutoSize: function() {
+            var choices = [
+                {value: "a", text: "A", data: 123},
+                {value: "b", text: "B", data: 456},
+                {value: "c", text: "C", data: 789}
+            ];
+            this.widget.set("choices", choices);
+            /* Without argument, autoSize() sets the size to the same
+               as the number of choices. */
+            this.widget.autoSize();
+            Assert.areEqual(3, this.widget.get("size"));
+        },
+
+        testAutoSizeMoreChoicesThanMaxiumum: function() {
+            var choices = [
+                {value: "a", text: "A", data: 123},
+                {value: "b", text: "B", data: 456},
+                {value: "c", text: "C", data: 789}
+            ];
+            this.widget.set("choices", choices);
+            /* autoSize() sets the size to the same as the number of
+               choices unless there are more than the specified
+               maximum. */
+            this.widget.autoSize(2);
+            Assert.areEqual(2, this.widget.get("size"));
+        },
+
+        testAutoSizeFewerChoicesThanMaxiumum: function() {
+            var choices = [
+                {value: "a", text: "A", data: 123},
+                {value: "b", text: "B", data: 456},
+                {value: "c", text: "C", data: 789}
+            ];
+            this.widget.set("choices", choices);
+            /* autoSize() sets the size to the same as the number of
+               choices. */
+            this.widget.autoSize(5);
+            Assert.areEqual(3, this.widget.get("size"));
+        },
+
         testMultiple: function() {
             Assert.areEqual(false, this.widget.get("multiple"));
         },
@@ -453,15 +614,28 @@
             var package_sets_collection =
                 new Y.lp.client.Collection(
                     null, {entries: package_sets}, null);
-            this.widget.set(
-                "packageSets",
-                package_sets_collection);
+            this.widget.set("packageSets", package_sets_collection);
             var choices = this.widget.get("choices");
             ArrayAssert.itemsAreEqual(
                 ["foo", "bar", "baz"],
                 attrselect("value")(choices));
         },
 
+        testSetPackageSetsCallsAutoSize: function() {
+            var package_sets = [
+                {name: "foo", description: "Foo"},
+                {name: "bar", description: "Bar"},
+                {name: "baz", description: "Baz"}
+            ];
+            var package_sets_collection =
+                new Y.lp.client.Collection(
+                    null, {entries: package_sets}, null);
+            var autoSized = false;
+            this.widget.autoSize = function() { autoSized = true; };
+            this.widget.set("packageSets", package_sets_collection);
+            Assert.isTrue(autoSized);
+        },
+
         testSetDistroSeriesInitiatesIO: function() {
             var io = false;
             this.widget.client = {