launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #05479
[Merge] lp:~deryck/launchpad/toggle-bug-fields-config-widget into lp:launchpad
Deryck Hodge has proposed merging lp:~deryck/launchpad/toggle-bug-fields-config-widget into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~deryck/launchpad/toggle-bug-fields-config-widget/+merge/81900
This branch adds a widget that acts as a config widget for the new CustomBugListings feature. This widget extends from BaseConfigUtil and adds a form overlay to the page when the widget's settings icon is clicked. After the form overlay opens, a user can click on several checkboxes to toggle the display of fields in a bug listing.
To see an example of this, visit this HTML prototype and click the settings cog in the order by bar.
http://people.canonical.com/~deryck/new-buglistings/new-buglistings.html
(Note that the html prototype is out dated from current work, but the interaction for this widget remains pretty close.)
This branch adds the widget itself and test coverage. It's not hooked up with lp yet, but I have another branch going that builds on this one for the integration work. As part of the integration work, I'll land any CSS this widget needs.
--
https://code.launchpad.net/~deryck/launchpad/toggle-bug-fields-config-widget/+merge/81900
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~deryck/launchpad/toggle-bug-fields-config-widget into lp:launchpad.
=== added file 'lib/lp/bugs/javascript/buglisting_utils.js'
--- lib/lp/bugs/javascript/buglisting_utils.js 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/javascript/buglisting_utils.js 2011-11-10 19:30:33 +0000
@@ -0,0 +1,282 @@
+/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
+
+YUI().add('lp.buglisting_utils', function(Y) {
+ /**
+ * A utiltiy for configuring the display of bug listings.
+ *
+ * The purpose of this widget is be a mechanism for turning
+ * fields on and off in a bug listing display. It extends
+ * from BaseConfigUtil, which provides the clickable settings
+ * icon. When the icon is clicked, a form overlay opens with
+ * various checkboxes for turning fields on and off.
+ *
+ * This doesn't actually change the display, though. It fires
+ * an event that the buglisting navigator will hook into to update
+ * the list's display.
+ *
+ * @module lp.buglisting_utils
+ */
+
+ // Constants.
+ var FORM = 'form',
+ FIELD_VISIBILITY = 'field_visibility';
+
+ /**
+ * BugListingConfigUtil is the main object used to manipulate
+ * a bug listing's display.
+ *
+ * @class BugListingConfigUtil
+ * @extends Y.lp.configutils.BaseConfigUtil
+ * @constructor
+ */
+ function BugListingConfigUtil() {
+ BugListingConfigUtil.superclass.constructor.apply(this, arguments);
+ }
+
+ BugListingConfigUtil.NAME = 'buglisting-config-util';
+
+ /**
+ * Object to reference defaults for field_visibility.
+ */
+ BugListingConfigUtil.field_visibility_defaults = {
+ show_bugtarget: true,
+ show_bug_heat: true,
+ show_id: true,
+ show_importance: true,
+ show_status: true,
+ show_title: true,
+ show_milestone_name: false
+ };
+
+ /**
+ * Object to reference display names for field_visibility
+ * form inputs.
+ */
+ BugListingConfigUtil.field_display_names = {
+ show_bugtarget: 'Bug target',
+ show_bug_heat: 'Bug heat',
+ show_id: 'Bug number',
+ show_importance: 'Importance',
+ show_status: 'Status',
+ show_title: 'Bug title',
+ show_milestone_name: 'Milestone name'
+
+ };
+
+ BugListingConfigUtil.ATTRS = {
+
+ /**
+ * A config for field visibility. This determines which
+ * fields are visibile in a bug listing.
+ *
+ * @attribute field_visibility
+ * @type Object
+ */
+ field_visibility: {
+ valueFn: function() {
+ return this.constructor.field_visibility_defaults;
+ },
+ setter: function(value) {
+ var defaults = this.constructor.field_visibility_defaults;
+ return Y.merge(defaults, value);
+ }
+ },
+
+ /**
+ * A reference to the form overlay used in the overlay.
+ *
+ * @attribute form
+ * @type Y.lazr.FormOverlay
+ * @default null
+ */
+ form: {
+ value: null
+ }
+ };
+
+ BugListingConfigUtil.INPUT_TEMPLATE = [
+ '<input type="checkbox" class="{name}" name="{name}" ',
+ 'value="{display_name}" {checked}> {display_name}<br />'].join('');
+
+ Y.extend(BugListingConfigUtil, Y.lp.configutils.BaseConfigUtil, {
+
+ /**
+ * Hook into the destroy lifecyle to ensure the form
+ * overlay is destroyed.
+ *
+ * @method destructor
+ */
+ destructor: function() {
+ if (Y.Lang.isValue(this.get(FORM))) {
+ var form = this.get(FORM);
+ this.set(FORM, null);
+ form.destroy();
+ }
+ },
+
+ /**
+ * Build the input nodes used on the form overlay.
+ *
+ * @method getFormInputs
+ */
+ getFormInputs: function() {
+ var fields = this.get(FIELD_VISIBILITY);
+ var display_names = this.constructor.field_display_names;
+ var nodes = [];
+ var item,
+ name,
+ display_name,
+ checked,
+ input_html,
+ input_node;
+ for (item in fields) {
+ if (fields.hasOwnProperty(item)) {
+ name = item;
+ display_name = display_names[item];
+ if (fields[item] === true) {
+ checked = 'checked';
+ } else {
+ checked = '';
+ }
+ input_html = Y.Lang.substitute(
+ this.constructor.INPUT_TEMPLATE,
+ {name: name, display_name: display_name,
+ checked: checked});
+ input_node = Y.Node.create(input_html);
+ nodes.push(input_node);
+ }
+ }
+ return new Y.NodeList(nodes);
+ },
+
+ /**
+ * Build the reset link for the form.
+ *
+ * Also, provide a click handler to reset the fields config.
+ *
+ * @method getResetLink
+ */
+ getResetLink: function() {
+ var link = Y.Node.create('<a></a>');
+ link.addClass('js-action');
+ link.addClass('reset-buglisting');
+ link.setContent('Reset to default');
+ link.on('click', function(e) {
+ var defaults = this.constructor.field_visibility_defaults;
+ this.set(FIELD_VISIBILITY, defaults);
+ var form = this.get(FORM);
+ form.hide();
+ this.destructor();
+ this._extraRenderUI();
+ }, this);
+ return link;
+ },
+
+ /**
+ * Build the form content for form overlay.
+ *
+ * @method buildFormContent
+ */
+ buildFormContent: function() {
+ var div = Y.Node.create(
+ '<div></div>').addClass('buglisting-opts');
+ var inputs = this.getFormInputs();
+ div.append(inputs);
+ var link = this.getResetLink();
+ div.append(link);
+ return div;
+ },
+
+ /**
+ * Hook up the global events we want to fire.
+ *
+ * We do these as a global event rather than listening for
+ * attribute change events to avoid having to have a reference
+ * to the widget in another widget.
+ *
+ * @method addListeners
+ */
+ addListeners: function() {
+ // Fire a buglisting-config-util:fields-changed event.
+ this.after('field_visibilityChange', function() {
+ var event_name = this.constructor.NAME + ':fields-changed';
+ Y.fire(event_name);
+ });
+ },
+
+ /**
+ * Process the data from the form overlay submit.
+ *
+ * data is an object whose members are the checked
+ * input elements from the form. data has the same members
+ * as field_visibility, so if the key is in data it should
+ * be set to true in field_visibility.
+ *
+ * @method handleOverlaySubmit
+ */
+ handleOverlaySubmit: function(data) {
+ var fields = Y.clone(this.constructor.field_visibility_defaults);
+ var member;
+ for (member in fields) {
+ if (fields.hasOwnProperty(member)) {
+ if (Y.Lang.isValue(data[member])) {
+ // If this field exists in data, set it true.
+ // in field_visibility.
+ fields[member] = true;
+ } else {
+ // Otherwise, set the member to false in
+ // field_visibility.
+ fields[member] = false;
+ }
+ }
+ }
+ this.set(FIELD_VISIBILITY, fields);
+ },
+
+ /**
+ * Hook in _extraRenderUI provided by BaseConfigUtil
+ * to add a form overlay to the widget.
+ *
+ * @method _extraRenderUI
+ */
+ _extraRenderUI: function() {
+ var form_content = this.buildFormContent();
+ var on_submit_callback = Y.bind(this.handleOverlaySubmit, this);
+ util_overlay = new Y.lazr.FormOverlay({
+ align: 'left',
+ headerContent: '<h2>Items to display</h2>',
+ centered: true,
+ form_content: form_content,
+ form_submit_button: Y.Node.create(
+ '<input type="submit" value="Update" ' +
+ 'class="update-buglisting" />'
+ ),
+ form_cancel_button: Y.Node.create(
+ '<button type="button" name="field.actions.cancel" ' +
+ 'class="lazr-neg lazr-btn" >Cancel</button>'
+ ),
+ form_submit_callback: on_submit_callback
+ });
+ this.set(FORM, util_overlay);
+ this.addListeners();
+ util_overlay.render();
+ util_overlay.hide();
+ },
+
+ /**
+ * Hook into _handleClick provided by BaseConfigUtil
+ * to show overlay when the settings cog icon is clicked.
+ *
+ * @method _handleClick
+ */
+ _handleClick: function() {
+ var form = this.get(FORM);
+ form.show();
+ }
+
+ });
+
+ var buglisting_utils = Y.namespace('lp.buglisting_utils');
+ buglisting_utils.BugListingConfigUtil = BugListingConfigUtil;
+
+}, '0.1', {'requires': ['lp.configutils', 'lazr.formoverlay']});
=== added file 'lib/lp/bugs/javascript/tests/test_buglisting_utils.html'
--- lib/lp/bugs/javascript/tests/test_buglisting_utils.html 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting_utils.html 2011-11-10 19:30:33 +0000
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+ <head>
+ <title>Tests for Buglisting Config Widgets</title>
+
+ <!-- YUI and test setup -->
+ <script type="text/javascript"
+ src="../../../../canonical/launchpad/icing/yui/yui/yui.js">
+ </script>
+ <link rel="stylesheet" href="../../../app/javascript/testing/test.css" />
+ <script type="text/javascript"
+ src="../../../app/javascript/testing/testrunner.js"></script>
+
+ <!-- Dependencies from our tree -->
+ <script type="text/javascript"
+ src="../../../app/javascript/configutils.js"></script>
+ <script type="text/javascript"
+ src="../../../app/javascript/formoverlay/formoverlay.js"></script>
+ <link rel="stylesheet"
+ href="../../../app/javascript/formoverlay/assets/formoverlay-core.css"/>
+ <script type="text/javascript"
+ src="../../../app/javascript/overlay/overlay.js"></script>
+
+ <!-- The module under test -->
+ <script type="text/javascript" src="../buglisting_utils.js"></script>
+
+ <!-- The test suite -->
+ <script type="text/javascript" src="test_buglisting_utils.js"></script>
+
+</head>
+<body class="yui3-skin-sam">
+ <ul id="suites">
+ <li>lp.buglisting_utils.test</li>
+ </ul>
+</body>
+</html>
=== added file 'lib/lp/bugs/javascript/tests/test_buglisting_utils.js'
--- lib/lp/bugs/javascript/tests/test_buglisting_utils.js 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting_utils.js 2011-11-10 19:30:33 +0000
@@ -0,0 +1,303 @@
+/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
+
+YUI.add('lp.buglisting_utils.test', function(Y) {
+
+var buglisting_utils = Y.namespace('lp.buglisting_utils.test');
+
+var suite = new Y.Test.Suite('BugListingConfigUtil Tests');
+
+var Assert = Y.Assert;
+var ArrayAssert = Y.ArrayAssert;
+var ObjectAssert = Y.ObjectAssert;
+
+suite.add(new Y.Test.Case({
+
+ name: 'buglisting_display_utils_tests',
+
+ tearDown: function() {
+ if (Y.Lang.isValue(this.list_util)) {
+ this.list_util.destroy();
+ }
+ },
+
+ /**
+ * Test helper to see what the form actually looks
+ * like on the page.
+ *
+ * It builds a list of show_xxx names and another
+ * list of booleans representing checked value.
+ */
+ getActualInputData: function() {
+ var actual_names = [];
+ var actual_checked = [];
+ var inputs = Y.one(
+ '.yui3-lazr-formoverlay-content form').all('input');
+ inputs.each(function(el) {
+ if (el.get('type') === 'checkbox') {
+ actual_names.push(el.get('name'));
+ actual_checked.push(el.get('checked'));
+ }
+ });
+ return [actual_names, actual_checked];
+ },
+
+ test_bug_listing_util_extends_base_util: function() {
+ // BugListingConfigUtil extends from BaseConfigUtil.
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+ Assert.isInstanceOf(Y.lp.configutils.BaseConfigUtil, this.list_util);
+ },
+
+ test_default_field_visibility_config: function() {
+ // The default field_visibility should exist in a new widget.
+ var expected_config = {
+ show_bugtarget: true,
+ show_bug_heat: true,
+ show_id: true,
+ show_importance: true,
+ show_status: true,
+ show_title: true,
+ show_milestone_name: false
+ };
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+ ObjectAssert.areEqual(
+ expected_config, this.list_util.get('field_visibility'));
+ },
+
+ test_supplied_field_visibility_config: function() {
+ // field_visibility can be changed at the call site.
+ // Supplied fields will be merged with the defaults.
+ var supplied_config = {
+ show_bugtarget: false,
+ show_bug_heat: false
+ };
+ var expected_config = {
+ show_bugtarget: false,
+ show_bug_heat: false,
+ show_id: true,
+ show_importance: true,
+ show_status: true,
+ show_title: true,
+ show_milestone_name: false
+ };
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
+ field_visibility: supplied_config
+ });
+ ObjectAssert.areEqual(
+ expected_config, this.list_util.get('field_visibility'));
+ },
+
+ test_field_visibility_form_reference: function() {
+ // The form created from field_visibility defaults is referenced
+ // via BugListingConfigUtil.get('form')
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+ Assert.isNotUndefined(this.list_util.get('form'));
+ },
+
+ test_field_visibility_form_shows_defaults: function() {
+ // The form should have a checkbox for every default item,
+ // and the checked value should match true or false values.
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+ this.list_util.render();
+ var expected_names = [
+ 'show_bugtarget',
+ 'show_bug_heat',
+ 'show_id',
+ 'show_importance',
+ 'show_status',
+ 'show_title',
+ 'show_milestone_name'
+ ];
+ var expected_checked = [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ false
+ ];
+ var actual_inputs = this.getActualInputData();
+ ArrayAssert.itemsAreSame(expected_names, actual_inputs[0]);
+ ArrayAssert.itemsAreSame(expected_checked, actual_inputs[1]);
+ },
+
+ test_field_visibility_form_shows_supplied_defaults: function() {
+ // The form checkboxes should also match the user supplied
+ // config values.
+ var field_visibility = {
+ show_bugtarget: false,
+ show_bug_heat: false
+ };
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
+ field_visibility: field_visibility
+ });
+ this.list_util.render();
+ var expected_names = [
+ 'show_bugtarget',
+ 'show_bug_heat',
+ 'show_id',
+ 'show_importance',
+ 'show_status',
+ 'show_title',
+ 'show_milestone_name'
+ ];
+ var expected_checked = [
+ false,
+ false,
+ true,
+ true,
+ true,
+ true,
+ false
+ ];
+ var actual_inputs = this.getActualInputData();
+ ArrayAssert.itemsAreSame(expected_names, actual_inputs[0]);
+ ArrayAssert.itemsAreSame(expected_checked, actual_inputs[1]);
+ },
+
+ test_click_icon_reveals_overlay: function() {
+ // Clicking the settings icon should reveal the form overlay.
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+ this.list_util.render();
+ var overlay = this.list_util.get('form').get('boundingBox');
+ Assert.isTrue(overlay.hasClass('yui3-lazr-formoverlay-hidden'));
+ var config = Y.one('.config');
+ config.simulate('click');
+ Assert.isFalse(overlay.hasClass('yui3-lazr-formoverlay-hidden'));
+ },
+
+ test_field_visibility_form_update_config: function() {
+ // Changing elements on the form also updates the field_visibility
+ // config values.
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+ this.list_util.render();
+ var config = Y.one('.config');
+ config.simulate('click');
+ var show_bugtarget = Y.one('.show_bugtarget');
+ var show_bug_heat = Y.one('.show_bug_heat');
+ show_bugtarget.simulate('click');
+ show_bug_heat.simulate('click');
+ var update = Y.one('.update-buglisting');
+ update.simulate('click');
+ var expected_config = {
+ show_bugtarget: false,
+ show_bug_heat: false,
+ show_id: true,
+ show_importance: true,
+ show_status: true,
+ show_title: true,
+ show_milestone_name: false
+ };
+ var actual_config = this.list_util.get('field_visibility');
+ ObjectAssert.areEqual(expected_config, actual_config);
+ },
+
+ test_update_config_fires_event: function() {
+ // A custom event fires when the field_visibility config
+ // is updated.
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+ this.list_util.render();
+ // Setup event handler.
+ var event_fired = false;
+ Y.on('buglisting-config-util:fields-changed', function(e) {
+ event_fired = true;
+ });
+ // Poke at the page to update the form.
+ var config = Y.one('.config');
+ config.simulate('click');
+ var show_bugtarget = Y.one('.show_bugtarget');
+ show_bugtarget.simulate('click');
+ var update = Y.one('.update-buglisting');
+ update.simulate('click');
+ // Confirm the event handler worked.
+ Assert.isTrue(event_fired);
+ },
+
+ test_fields_visibility_form_reset: function() {
+ // Clicking "reset to defaults" on the form returns
+ // field_visibility to its default values.
+ var field_visibility = {
+ show_bugtarget: false,
+ show_bug_heat: false
+ };
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
+ field_visibility: field_visibility
+ });
+ this.list_util.render();
+ // Setup event handler.
+ var event_fired = false;
+ Y.on('buglisting-config-util:fields-changed', function(e) {
+ event_fired = true;
+ // Confirm that field_visibility is now the same as the defaults.
+ var defaults = this.list_util.field_visibility_defaults;
+ var fields = this.list_util.get('field_visibility');
+ ObjectAssert.areEqual(defaults, fields);
+ }, this);
+ // Poke at the page to reset the form.
+ var config = Y.one('.config');
+ config.simulate('click');
+ Y.one('.reset-buglisting').simulate('click');
+ Assert.isTrue(event_fired);
+ },
+
+ test_fields_visibility_form_reset_hides_overlay: function() {
+ // Reseting to defaults should hide the form overlay.
+ var field_visibility = {
+ show_bugtarget: false,
+ show_bug_heat: false
+ };
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
+ field_visibility: field_visibility
+ });
+ this.list_util.render();
+ // Poke at the form to reset defaults.
+ var config = Y.one('.config');
+ config.simulate('click');
+ Y.one('.reset-buglisting').simulate('click');
+ var overlay = this.list_util.get('form').get('boundingBox');
+ Assert.isTrue(overlay.hasClass('yui3-lazr-formoverlay-hidden'));
+ },
+
+ test_fields_visibility_form_reset_updates_form: function() {
+ // Reseting to defaults should reset the form inputs, too.
+ var field_visibility = {
+ show_bugtarget: false,
+ show_bug_heat: false
+ };
+ this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
+ field_visibility: field_visibility
+ });
+ this.list_util.render();
+ // Poke at the form to reset defaults.
+ var config = Y.one('.config');
+ config.simulate('click');
+ Y.one('.reset-buglisting').simulate('click');
+ var expected_names = [
+ 'show_bugtarget',
+ 'show_bug_heat',
+ 'show_id',
+ 'show_importance',
+ 'show_status',
+ 'show_title',
+ 'show_milestone_name'
+ ];
+ var expected_checked = [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ false
+ ];
+ var actual_inputs = this.getActualInputData();
+ ArrayAssert.itemsAreSame(expected_names, actual_inputs[0]);
+ ArrayAssert.itemsAreSame(expected_checked, actual_inputs[1]);
+ }
+
+}));
+
+buglisting_utils.suite = suite;
+
+}, '0.1', {'requires': [
+ 'test', 'node-event-simulate', 'lp.buglisting_utils']});