← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wallyworld/launchpad/naughty-expanders into lp:launchpad

 

Ian Booth has proposed merging lp:~wallyworld/launchpad/naughty-expanders into lp:launchpad.

Requested reviews:
  Curtis Hovey (sinzui)
Related bugs:
  Bug #715892 in Launchpad itself: "JavaScript for showing license sections is fragile and lacks tests"
  https://bugs.launchpad.net/launchpad/+bug/715892
  Bug #916053 in Launchpad itself: "Expander widget defaults to unseen so requires javascript to display"
  https://bugs.launchpad.net/launchpad/+bug/916053

For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/naughty-expanders/+merge/123471

== Implementation ==

1. Visibility of collapsed/hidden elements on non-js browsers

Some items using the Expander widget are meant to be hidden when the page loads, and able to be expanded by the user. However, when the browser has no js, these items stay hidden and cannot be made visible.

Luckily, yui adds a class - yui3-js-enabled - to the <html> element when js is supported. So a new css class can be defined:

.yui3-js-enabled .hide-on-load {
    display: none;
    }

Instead of marking expander elements as 'hidden', they are marked as 'hide-on-load'. Such elements will remain visible when there is no js but start out hidden when there is js. The expander initialisation code replaces 'hide-on-load' with 'hidden' so that subsequent internal processing works as expected.

So all elements marked with css class 'collapsible', and hence processed by the Expander widget, have had 'hidden' replaced with 'hide-on-load' where necessary.

2. Product licence editing

When marking the changes above, I saw that the collapsible product licence elements were handled by some old, crufty javascript embedded in the TAL. The embedded javascript did 2 things:
i) bespoke expander implementation
ii) hide/show stuff depending on what licences are chosen

The bespoke expander stuff can just be deleted and replaced with the standard lp expander stuff, allowing a bunch of code to be deleted.
The hide/show stuff was moved to a new module and tests were added.

It appears to me that the DEPRECATED licence section is never used anymore. There is only one licence that is marked as deprecated that I can see - PERL. And as far as I can tell, the widget view's deprecated_count property is always 0. So I deleted the TAL and view code. 

== Tests ==

New yui tests for licence javascript.

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/canonical/launchpad/icing/css/modifiers.css
  lib/lp/app/javascript/expander.js
  lib/lp/app/javascript/licence/
  lib/lp/app/javascript/licence/licence.js
  lib/lp/app/javascript/licence/tests/
  lib/lp/app/javascript/licence/tests/test_licence.html
  lib/lp/app/javascript/licence/tests/test_licence.js
  lib/lp/app/widgets/product.py
  lib/lp/app/widgets/templates/license.pt
  lib/lp/code/templates/branch-register-merge.pt
  lib/lp/code/templates/sourcepackagerecipe-related-branches.pt
  lib/lp/registry/templates/product-files.pt
  lib/lp/registry/templates/productrelease-portlet-data.pt
-- 
https://code.launchpad.net/~wallyworld/launchpad/naughty-expanders/+merge/123471
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.
=== modified file 'lib/canonical/launchpad/icing/css/modifiers.css'
--- lib/canonical/launchpad/icing/css/modifiers.css	2012-08-10 04:48:36 +0000
+++ lib/canonical/launchpad/icing/css/modifiers.css	2012-09-10 03:52:20 +0000
@@ -256,3 +256,8 @@
 .hidden {
     display: none;
     }
+
+/* Some things we want to hide by default only if javascript is enabled. */
+.yui3-js-enabled .hide-on-load {
+    display: none;
+    }

=== modified file 'lib/lp/app/javascript/expander.js'
--- lib/lp/app/javascript/expander.js	2012-07-07 14:00:30 +0000
+++ lib/lp/app/javascript/expander.js	2012-09-10 03:52:20 +0000
@@ -146,6 +146,10 @@
     if (!Y.Lang.isObject(content_node)) {
         throw new Error("No content node given.");
     }
+    if (content_node.hasClass('hide-on-load')) {
+        content_node.removeClass('hide-on-load');
+        content_node.addClass(this.css_classes.hidden);
+    }
     this.icon_node = icon_node;
     this.content_node = content_node;
     if (Y.Lang.isValue(config)) {

=== added directory 'lib/lp/app/javascript/licence'
=== added file 'lib/lp/app/javascript/licence/licence.js'
--- lib/lp/app/javascript/licence/licence.js	1970-01-01 00:00:00 +0000
+++ lib/lp/app/javascript/licence/licence.js	2012-09-10 03:52:20 +0000
@@ -0,0 +1,88 @@
+/* Copyright 2012 Canonical Ltd.  This software is licensed under the
+ * GNU Affero General Public License version 3 (see the file LICENSE).
+ *
+ * @module lp.app.licence
+ * @requires node, event
+ */
+
+YUI.add('lp.app.licence', function(Y) {
+
+var namespace = Y.namespace('lp.app.licence');
+
+/**
+ * A widget to provide the click handling for product licences.
+ * This widget does no rendering itself; it is used to enhance existing HTML.
+ */
+namespace.LicenceWidget = Y.Base.create("licenceWidget", Y.Widget, [], {
+    bindUI: function() {
+        // Set a click event handler for the div containing all the
+        // licence checkbox.  When any licence checkbox is selected, the
+        // "I haven't specified the licence yet" radio button is set to
+        // "This project consists of code licensed under:".  However note
+        // that the pending-div only shows up if the project has never
+        // selected a licence.  In other cases, there's an "I don't know yet"
+        // choice in the "Other choices" licence section.
+        var license_pending = Y.one('#license_pending');
+        if (Y.Lang.isValue(license_pending)) {
+            license_pending.on('click', function(e) {
+                Y.all('[name="field.licenses"]').set('checked', false);
+                reveal_details();
+            });
+            var div = Y.one('#pending-div');
+            if (Y.Lang.isValue(div)) {
+                div.delegate('click', function(e) {
+                    Y.one('#license_pending').set('checked', false);
+                    Y.one('#license_complete').set('checked', true);
+               }, '[type=checkbox][name="field.licenses"]');
+            }
+        }
+
+        // When Other/Proprietary or Other/Open Source is chosen, the
+        // license_info widget is displayed.
+        var other_com = Y.one('input[value="OTHER_PROPRIETARY"]');
+        var other_os = Y.one('input[value="OTHER_OPEN_SOURCE"]');
+        var details = Y.one('#license-details');
+        var proprietary = Y.one('#proprietary');
+
+        var that = this;
+        function reveal_details() {
+            var cfg = {};
+            if (!that.get('use_animation')) {
+                cfg.duration = 0;
+            }
+            if (other_com.get('checked') || other_os.get('checked')) {
+                if (!details.hasClass('lazr-opened')) {
+                    Y.lazr.effects.slide_out(details, cfg).run();
+                }
+            } else {
+                if (!details.hasClass('lazr-closed')) {
+                    Y.lazr.effects.slide_in(details, cfg).run();
+                }
+            }
+            if (other_com.get('checked')) {
+                if (!proprietary.hasClass('lazr-opened')) {
+                    Y.lazr.effects.slide_out(proprietary, cfg).run();
+                }
+            } else {
+                if (!proprietary.hasClass('lazr-closed')) {
+                    Y.lazr.effects.slide_in(proprietary, cfg).run();
+                }
+            }
+        }
+        other_com.on('click', reveal_details);
+        other_os.on('click', reveal_details);
+        proprietary.removeClass('hidden');
+
+        // Pre-reveal license_info widget.
+        reveal_details();
+    }
+}, {
+    ATTRS: {
+        // Disable for tests.
+        use_animation: true
+    }
+});
+
+}, "0.1", {
+    "requires": ["base", "event", "node", "lazr.effects"]
+});

=== added directory 'lib/lp/app/javascript/licence/tests'
=== added file 'lib/lp/app/javascript/licence/tests/test_licence.html'
--- lib/lp/app/javascript/licence/tests/test_licence.html	1970-01-01 00:00:00 +0000
+++ lib/lp/app/javascript/licence/tests/test_licence.html	2012-09-10 03:52:20 +0000
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<!--
+Copyright 2012 Canonical Ltd.  This software is licensed under the
+GNU Affero General Public License version 3 (see the file LICENSE).
+-->
+
+<html>
+  <head>
+      <title>lp.app.licence Tests</title>
+
+      <!-- YUI and test setup -->
+      <script type="text/javascript"
+              src="../../../../../../build/js/yui/yui/yui.js">
+      </script>
+      <link rel="stylesheet"
+      href="../../../../../../build/js/yui/console/assets/console-core.css" />
+      <link rel="stylesheet"
+      href="../../../../../../build/js/yui/console/assets/skins/sam/console.css" />
+      <link rel="stylesheet"
+      href="../../../../../../build/js/yui/test/assets/skins/sam/test.css" />
+
+      <script type="text/javascript"
+              src="../../../../../../build/js/lp/app/testing/testrunner.js"></script>
+      <script type="text/javascript"
+              src="../../../../../../build/js/lp/app/testing/helpers.js"></script>
+
+      <link rel="stylesheet" href="../../../../app/javascript/testing/test.css" />
+
+      <!-- Dependencies -->
+      <script type="text/javascript" src="../../../../../../build/js/lp/app/anim/anim.js"></script>
+      <script type="text/javascript" src="../../../../../../build/js/lp/app/effects/effects.js"></script>
+
+
+      <!-- The module under test. -->
+      <script type="text/javascript" src="../licence.js"></script>
+
+      <!-- Placeholder for any css asset for this module. -->
+      <!-- <link rel="stylesheet" href="../assets/lp.bugs.bug_picker-core.css" /> -->
+
+      <!-- The test suite -->
+      <script type="text/javascript" src="test_licence.js"></script>
+
+    </head>
+    <body class="yui3-skin-sam">
+        <ul id="suites">
+            <li>lp.app.licence.test</li>
+        </ul>
+        <div id="fixture">
+        </div>
+        <script type="text/x-template" id="licence-fixture">
+            <div>
+            <input id="license_pending" name="license_status" type="radio" checked="checked" />
+            <label for="license_pending">I haven't specified the licence yet</label>
+            <input id="license_complete" name="license_status" type="radio" />
+            <label for="license_complete">
+              This project consists of code licensed under:
+            </label>
+            </div>
+            <div id="pending-div">
+            <div class="collapsible">
+            <legend>Recommended open source licences</legend>
+            <div id="recommended" class="hide-on-load expanded">
+            <table><tr>
+            <td><label for="field.licenses.1" style="font-weight: normal">
+                <input class="checkboxType" id="field.licenses.1" name="field.licenses"
+                       type="checkbox" value="LICENCE_1" /></label> </td>
+            <td><label for="field.licenses.2" style="font-weight: normal">
+                <input class="checkboxType" id="field.licenses.2" name="field.licenses"
+                       type="checkbox" value="LICENCE_2" /></label> </td>
+            </tr></table>
+            </div>
+            </div>
+
+            <div class="collapsible">
+            <legend>Other choices</legend>
+            <div id="special" class="hide-on-load "><table>
+            <tr>
+            <td><label for="field.licenses.3" style="font-weight: normal">
+                <input class="checkboxType" id="field.licenses.3" name="field.licenses"
+                       type="checkbox" value="OTHER_PROPRIETARY" /></label> </td>
+            <td><label for="field.licenses.4" style="font-weight: normal">
+                <input class="checkboxType" id="field.licenses.4" name="field.licenses"
+                       type="checkbox" value="OTHER_OPEN_SOURCE" /></label> </td>
+            </tr></table>
+            </div>
+            </div>
+            <div id="license-details" class="hide-on-load">
+              <label for="field.license_info">Licence details:</label>
+              <textarea id="field.license_info" name="field.license_info"></textarea>
+            </div>
+            </div>
+            <div id="proprietary" class="hidden">
+              Commercial and proprietary projects do not qualify for free hosting;
+              therefore a subscription needs to be purchased in order to host this
+              project on Launchpad.
+            </div>
+        </script>
+    </body>
+</html>

=== added file 'lib/lp/app/javascript/licence/tests/test_licence.js'
--- lib/lp/app/javascript/licence/tests/test_licence.js	1970-01-01 00:00:00 +0000
+++ lib/lp/app/javascript/licence/tests/test_licence.js	2012-09-10 03:52:20 +0000
@@ -0,0 +1,135 @@
+/* Copyright (c) 2012, Canonical Ltd. All rights reserved. */
+
+YUI.add('lp.app.licence.test', function (Y) {
+
+    var tests = Y.namespace('lp.app.licence.test');
+    var ns = Y.lp.app.licence;
+    tests.suite = new Y.Test.Suite(
+        'lp.app.licence Tests');
+
+    tests.suite.add(new Y.Test.Case({
+        name: 'lp.app.licence_tests',
+
+        setUp: function () {
+            this.fixture = Y.one('#fixture');
+            var form = Y.Node.create(Y.one('#licence-fixture').getContent());
+            this.fixture.appendChild(form);
+        },
+
+        tearDown: function () {
+            if (Y.Lang.isValue(this.widget)) {
+                delete this.widget;
+            }
+            if (Y.Lang.isValue(this.fixture)) {
+                this.fixture.empty(true);
+                delete this.fixture;
+            }
+        },
+
+        test_library_exists: function () {
+            Y.Assert.isObject(Y.lp.app.licence,
+                "Could not locate the lp.app.licence module");
+        },
+
+        makeWidget: function() {
+            this.widget = new Y.lp.app.licence.LicenceWidget({
+                use_animation: false
+            });
+            this.widget.render();
+            var finish = function() {};
+            this.wait(finish, 20);
+        },
+
+        // Test the visibility of the element for the given css selector.
+        _assert_animated_state: function(showing, selector) {
+            var check = function() {
+                var licence_details = Y.one(selector);
+                if (showing) {
+                    Y.Assert.isTrue(licence_details.hasClass('lazr-opened'));
+                    Y.Assert.areEqual(
+                            'visible', licence_details.getStyle('overflow'));
+                } else {
+                    Y.Assert.isTrue(licence_details.hasClass('lazr-closed'));
+                    Y.Assert.areEqual(
+                            'hidden', licence_details.getStyle('overflow'));
+                }
+            };
+            this.wait(check, 20);
+        },
+
+        _assert_animated_state_licence_details: function(showing) {
+            this._assert_animated_state(showing, '#license-details');
+        },
+
+        _assert_animated_state_proprietary: function(showing) {
+            this._assert_animated_state(showing, '#proprietary');
+        },
+
+        // The widget is created as expected.
+        test_create_widget: function() {
+            this.makeWidget();
+            Y.Assert.isInstanceOf(
+                ns.LicenceWidget, this.widget,
+                "Licence widget failed to be instantiated");
+            this._assert_animated_state_licence_details(false);
+            this._assert_animated_state_proprietary(false);
+        },
+
+        // When a licence is selected, the pending/complete radio buttons
+        // are correctly toggled.
+        test_click_licence_selects_license_complete: function() {
+            this.makeWidget();
+            Y.one('#license_pending').simulate('click');
+            Y.one('[id="field.licenses.1"]').simulate('click');
+            Y.Assert.isFalse(Y.one('#license_pending').get('checked'));
+            Y.Assert.isTrue(Y.one('#license_complete').get('checked'));
+            this._assert_animated_state_licence_details(false);
+            this._assert_animated_state_proprietary(false);
+        },
+
+        // Any selected licences are unselected when the pending radio button
+        // is clicked.
+        test_click_license_pending_unselects_licences: function() {
+            this.makeWidget();
+            // Click once to select.
+            Y.one('[id="field.licenses.3"]').simulate('click');
+            // Click again to unselect.
+            Y.one('[id="field.licenses.3"]').simulate('click');
+            Y.one('#license_pending').simulate('click');
+            Y.Assert.isFalse(Y.one('[id="field.licenses.1"]').get('checked'));
+            this._assert_animated_state_licence_details(false);
+            this._assert_animated_state_proprietary(false);
+        },
+
+        // The licence details field is shown when a licence type of 'other'
+        // is selected..
+        test_other_license_shows_details: function() {
+            this.makeWidget();
+            Y.each(['OTHER_PROPRIETARY', 'OTHER_OPEN_SOURCE'],
+                function(licence_type) {
+                    var licence = Y.one('input[value="' + licence_type + '"]');
+                    // Click once to select.
+                    licence.simulate('click');
+                    this._assert_animated_state_licence_details(true);
+                    // Click again to unselect.
+                    licence.simulate('click');
+                    this._assert_animated_state_licence_details(false);
+            });
+        },
+
+        // The proprietary help text is shown when a licence type of 'other
+        // proprietary' is selected..
+        test_proprietary_license_shows_proprietary_help: function() {
+            this.makeWidget();
+            var licence = Y.one('input[value="OTHER_PROPRIETARY"]');
+            // Click once to select.
+            licence.simulate('click');
+            this._assert_animated_state_proprietary(true);
+            // Click again to unselect.
+            licence.simulate('click');
+            this._assert_animated_state_proprietary(false);
+        }
+    }));
+
+}, '0.1', {'requires': [
+    'test', 'console', 'event', 'node-event-simulate', 'lp.app.licence']});

=== modified file 'lib/lp/app/widgets/product.py'
--- lib/lp/app/widgets/product.py	2012-06-15 16:23:50 +0000
+++ lib/lp/app/widgets/product.py	2012-09-10 03:52:20 +0000
@@ -300,7 +300,6 @@
         'ZPL': 'more',
         'CC_BY': 'more',
         'CC_BY_SA': 'more',
-        'PERL': 'deprecated',
         'OTHER_PROPRIETARY': 'special',
         'OTHER_OPEN_SOURCE': 'special',
         'DONT_KNOW': 'special',
@@ -330,15 +329,6 @@
             prefix='field', value=initial_value,
             context=field.context)
         self.source_package_release = None
-        # These will get filled in by _categorize().  They are the number of
-        # selected licences in the category.  The actual count doesn't matter,
-        # since if it's greater than 0 it will start opened.  NOte that we
-        # always want the recommended licences to be opened, so we initialize
-        # its value to 1.
-        self.recommended_count = 1
-        self.more_count = 0
-        self.deprecated_count = 0
-        self.special_count = 0
 
     def textForValue(self, term):
         """See `ItemsWidgetBase`."""
@@ -351,8 +341,8 @@
             return value
         else:
             return structured(
-                '%s&nbsp;<a href="%s" class="sprite external-link action-icon">'
-                'view licence</a>'
+                '%s&nbsp;<a href="%s" class="sprite external-link action-icon"'
+                '>view licence</a>'
                 % (value, term.value.url))
 
     def renderItem(self, index, text, value, name, cssClass):
@@ -396,16 +386,17 @@
         # manually.
         # pylint: disable-msg=E1002
         super(LicenseWidget, self).__call__()
-        self.recommended = self._renderTable('recommended', 3)
+        self.recommended = self._renderTable('recommended', 3, True)
         self.more = self._renderTable('more', 3)
-        self.deprecated = self._renderTable('deprecated')
         self.special = self._renderTable('special')
         return self.template()
 
-    def _renderTable(self, category, column_count=1):
+    def _renderTable(self, category, column_count=1, start_opened=False):
         # The tables are wrapped in divs, since IE8 does not respond
         # to setting the table's height to zero.
-        html = ['<div id="%s"><table>' % category]
+        klass = 'expanded' if start_opened else ''
+        html = [
+            '<div id="%s" class="hide-on-load %s"><table>' % (category, klass)]
         rendered_items = self.items_by_category[category]
         row_count = int(math.ceil(len(rendered_items) / float(column_count)))
         for i in range(0, row_count):

=== modified file 'lib/lp/app/widgets/templates/license.pt'
--- lib/lp/app/widgets/templates/license.pt	2012-07-07 14:00:30 +0000
+++ lib/lp/app/widgets/templates/license.pt	2012-09-10 03:52:20 +0000
@@ -3,194 +3,32 @@
   xmlns:metal="http://xml.zope.org/namespaces/metal";
   xmlns:i18n="http://xml.zope.org/namespaces/i18n";
   omit-tag="">
-<script type="text/javascript"
-        tal:condition="view/allow_pending_license">
-//<![CDATA[
-function license_chosen() {
-  document.getElementById('license_pending').checked = false;
-  document.getElementById('license_complete').checked = true;
-}
-
-function unset_licenses() {
-  var checkboxes = document.getElementsByName('field.licenses');
-  for (var i = 0; i < checkboxes.length; i++) {
-    checkboxes[i].checked = false;
-  }
-}
-//]]>
-</script>
 <script type="text/javascript">
-//<![CDATA[
-// XXX: deryck 2011-02-09 bug=715892
-// This stuff really needs a rewrite.  It is fragile
-// and breaks with every YUI/lazr-js upgrade.
-// Tread carefully, or else please make this better
-// and add tests, too (preferably in reverse order there.)
-LPJS.use('node', 'lazr.effects', function(Y) {
-    Y.on('domready', function() {
-        function make_slider(cfg) {
-            var table_name = '#' + cfg.which;
-            var target_name = table_name + '-expand';
-            var arrow_name = target_name + '-arrow';
-            var target = Y.one(target_name);
-
-            // Initialize the slider state.
-            var table = Y.one(table_name);
-            if (Y.Lang.isValue(table)) {
-                // 2009-06-15 BarryWarsaw: For some reason nobody can explain,
-                // this is required for animations to work properly.  Without
-                // this, nothing happens!
-                table.setStyle('display', 'block');
-
-                // The table starts either collapsed or expanded.  The
-                // animation lives on the link target.  The initial state of
-                // the slider depends on whether there are any checked
-                // licences in that category.  A '0' means 'no'.
-                var arrow = Y.one(arrow_name);
-                if (arrow.getAttribute('start_expanded') == '0') {
-                    target.slide = Y.lazr.effects.slide_in(table_name);
-                }
-                else {
-                    // This is wrong on so many levels.  The view should
-                    // set the classes such that the slider is initialized
-                    // properly without requiring we run hiden animations.
-                    //
-                    // That is a largish refactor, so until that can happen,
-                    // hard code the heights.  They cannot be determined with
-                    // 'scrollHeight' because the elements are not visible.
-                    var expanded_height;
-                    if (cfg.which == 'more') {
-                        expanded_height = 120;
-                    } else {
-                        expanded_height = 65;
-                    }
-                    target.slide = Y.lazr.effects.slide_out(
-                        table_name, {to: {height: expanded_height}});
-                }
-                target.slide.stop();
-                target.slide.run();
-
-                // This event handler toggles the arrow state once the
-                // animation is complete.  drawer_closed is an undocumented
-                // attribute used by lazr-js, and recommended by mars.
-                target.slide.on('end', function() {
-                    var src;
-                    if (this.drawer_closed) {
-                        src = '/@@/treeCollapsed';
-                    }
-                    else {
-                        src = '/@@/treeExpanded';
-                    }
-                    Y.one(arrow_name).setAttribute('src', src);
-                });
-            }
-
-            // This is the event handler for clicking on the link or arrow.
-            // It runs the slide animation, toggling between forward and
-            // reverse.
-            Y.on('click', function(e) {
-                e.preventDefault();
-                // We have to pass in the height by hand if the tables
-                // are not visible when the page loads.
-                var visible_on_load = !Y.one(
-                    '#launchpad-form-widgets').hasClass('hidden');
-                if (visible_on_load) {
-                    target.slide.set('reverse', !target.slide.get('reverse'));
-                } else {
-                    var info_height = target.get(
-                        'nextElementSibling').get('scrollHeight');
-                    target.slide.set('to', {height: info_height});
-                    target.slide.set('reverse', !target.slide.drawer_closed);
-                }
-                target.slide.stop();
-                target.slide.run();
-            }, target_name);
-        }
-        make_slider({which: 'copyright'});
-        make_slider({which: 'recommended'});
-        make_slider({which: 'more'});
-        make_slider({which: 'deprecated'});
-        make_slider({which: 'special'});
-
-        // Set a click event handler for the div containing all the
-        // licence checkbox.  When any licence checkbox is selected, the
-        // "I haven't specified the licence yet" radio button is set to
-        // "This project consists of code licensed under:".  However note
-        // that the pending-div only shows up if the project has never
-        // selected a licence.  In other cases, there's an "I don't know yet"
-        // choice in the "Other choices" licence section.
-        var license_pending = Y.one('#license_pending');
-        if (Y.Lang.isValue(license_pending)) {
-            license_pending.on('click', reveal_details);
-
-            var div = Y.one('#pending-div');
-            if (Y.Lang.isValue(div)) {
-                div.on('click', license_chosen);
-            }
-        }
-
-        // When Other/Proprietary or Other/Open Source is chosen, the
-        // license_info widget is displayed.
-        var other_com = Y.one('input[value="OTHER_PROPRIETARY"]');
-        var other_os = Y.one('input[value="OTHER_OPEN_SOURCE"]');
-        var details = Y.one('#license-details');
-        var proprietary = Y.one('#proprietary');
-
-        function reveal_details(e) {
-            if (other_com.get('checked') || other_os.get('checked')) {
-                if (!details.hasClass('lazr-opened')) {
-                    Y.lazr.effects.slide_out(details).run();
-                }
-            } else {
-                if (!details.hasClass('lazr-closed')) {
-                    Y.lazr.effects.slide_in(details).run();
-                }
-            }
-            if (other_com.get('checked')) {
-                if (!proprietary.hasClass('lazr-opened')) {
-                    Y.lazr.effects.slide_out(proprietary).run();
-                }
-            } else {
-                if (!proprietary.hasClass('lazr-closed')) {
-                    Y.lazr.effects.slide_in(proprietary).run();
-                }
-            }
-        }
-        other_com.on('click', reveal_details);
-        other_os.on('click', reveal_details);
-        proprietary.removeClass('hidden');
-
-        // Pre-reveal license_info widget.
-        reveal_details(true);
-    });
-});
-//]]>
+  LPJS.use('lp.app.licence', function(Y) {
+      Y.on('domready', function() {
+        var widget = new Y.lp.app.licence.LicenceWidget();
+        widget.render();
+      });
+  });
 </script>
 <div style="color: black">
   <tal:copyright condition="view/source_package_release">
-    <a href="" id="copyright-expand" class="js-action">
-      <img id="copyright-expand-arrow"
-            src="/@@/treeCollapsed"
-            title="Copyright info from source package"
-            alt="Copyright info from source package"
-            start_expanded="0"/>
-      Copyright info from source package
-    </a>
-    <div id="copyright">
-      <div
-        tal:content="structure view/source_package_release/@@+copyright"
+    <div class="collapsible">
+    <legend>Copyright info from source package</legend>
+    <div id="copyright" class="hide-on-load">
+      <div tal:content="structure view/source_package_release/@@+copyright"
         style="overflow-x: hidden; overflow-y: auto;
                max-width: 60em; max-height: 32em; background: #f7f7f7"
         />
     </div>
+    </div>
   </tal:copyright>
 
   Select the licence(s) under which you release your project.
   <div tal:condition="view/allow_pending_license"
        tal:define="is_empty not:view/_getFormInput">
     <input id="license_pending" name="license_status" type="radio"
-           tal:attributes="checked is_empty"
-            onClick="unset_licenses()" />
+           tal:attributes="checked is_empty"/>
     <label for="license_pending">I haven't specified the licence yet</label>
     <br/>
 
@@ -202,45 +40,21 @@
   </div>
 
   <div id="pending-div" style="padding-left: 20px">
-    <a href="" id="recommended-expand" class="js-action">
-      <img id="recommended-expand-arrow"
-           src="/@@/treeExpanded"
-           title="Recommended open source licences"
-           alt="Recommended open source licences"
-           tal:attributes="start_expanded view/recommended_count"/>
-      Recommended open source licences
-    </a>
+    <div class="collapsible">
+    <legend>Recommended open source licences</legend>
     <input tal:replace="structure view/recommended" />
-    <a href="" id="more-expand" class="js-action">
-      <img id="more-expand-arrow"
-           src="/@@/treeCollapsed"
-           title="More open source licences"
-           alt="More open source licences"
-           tal:attributes="start_expanded view/more_count"/>
-      More open source licences
-    </a>
+    </div>
+
+    <div class="collapsible">
+    <legend>More open source licences</legend>
     <input tal:replace="structure view/more" />
-    <div tal:condition="view/deprecated_count">
-      <a href="" id="deprecated-expand" class="js-action">
-        <img id="deprecated-expand-arrow"
-             src="/@@/treeCollapsed"
-             title="Deprecated licences"
-             alt="Deprecated licences"
-             tal:attributes="start_expanded view/deprecated_count"/>
-        Deprecated licences
-      </a>
-      <input tal:replace="structure view/deprecated" />
     </div>
-    <a href="" id="special-expand" class="js-action">
-      <img id="special-expand-arrow"
-           src="/@@/treeCollapsed"
-           title="Other choices"
-           alt="Other choices"
-           tal:attributes="start_expanded view/special_count"/>
-      Other choices
-    </a>
+
+    <div class="collapsible">
+    <legend>Other choices</legend>
     <input tal:replace="structure view/special" />
-    <div id="license-details" style="margin-top: 10px">
+    </div>
+    <div id="license-details" class="hide-on-load" style="margin-top: 10px">
       <label for="field.license_info">Licence details:</label>
       <input tal:replace="structure view/license_info_widget" />
       <p class="formHelp">Additional licence details are required.

=== modified file 'lib/lp/code/templates/branch-register-merge.pt'
--- lib/lp/code/templates/branch-register-merge.pt	2012-07-07 14:00:30 +0000
+++ lib/lp/code/templates/branch-register-merge.pt	2012-09-10 03:52:20 +0000
@@ -32,9 +32,8 @@
               <fieldset id="mergeproposal-extra-options"
                         class="collapsible">
                 <legend>Extra options</legend>
-                <div class="hidden"><!-- hidden by default -->
+                <div class="hide-on-load"><!-- hidden by default -->
                 <table class="extra-options">
-
                   <tal:widget define="widget nocall:view/widgets/commit_message">
                     <metal:block use-macro="context/@@launchpad_form/widget_row" />
                   </tal:widget>
@@ -46,9 +45,6 @@
                   <tal:widget define="widget nocall:view/widgets/prerequisite_branch">
                     <metal:block use-macro="context/@@launchpad_form/widget_row" />
                   </tal:widget>
-
-
-
                 </table>
                 </div>
               </fieldset>

=== modified file 'lib/lp/code/templates/sourcepackagerecipe-related-branches.pt'
--- lib/lp/code/templates/sourcepackagerecipe-related-branches.pt	2012-07-07 14:00:30 +0000
+++ lib/lp/code/templates/sourcepackagerecipe-related-branches.pt	2012-09-10 03:52:20 +0000
@@ -5,7 +5,7 @@
                           packageBranches view/related_package_branch_info"
               tal:condition="python: seriesBranches or packageBranches">
   <legend>Related branches</legend>
-  <div class="extra-options hidden">
+  <div class="extra-options hide-on-load">
 
     <div tal:condition="packageBranches" id="related-package-branches">
       <h2>Source package branches</h2>

=== modified file 'lib/lp/registry/templates/product-files.pt'
--- lib/lp/registry/templates/product-files.pt	2012-07-07 14:00:30 +0000
+++ lib/lp/registry/templates/product-files.pt	2012-09-10 03:52:20 +0000
@@ -69,7 +69,7 @@
 
                   <div class="collapsible">
                     <div>Release information</div>
-                    <div class="hidden">
+                    <div class="hide-on-load">
                     <div tal:condition="release/release_notes">
                       <strong>Release notes:</strong>
                       <div style="margin-bottom: 0px;"

=== modified file 'lib/lp/registry/templates/productrelease-portlet-data.pt'
--- lib/lp/registry/templates/productrelease-portlet-data.pt	2012-07-07 14:00:30 +0000
+++ lib/lp/registry/templates/productrelease-portlet-data.pt	2012-09-10 03:52:20 +0000
@@ -84,7 +84,7 @@
     <div class="collapsible" tal:condition="view/release/changelog">
       <div>View the full changelog</div>
 
-      <div id="changelog" class="hidden"
+      <div id="changelog" class="hide-on-load"
         tal:content="structure view/release/changelog/fmt:obfuscate-email/fmt:text-to-html">
         ProductRelease.changelog.
       </div>


Follow ups