← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~abentley/launchpad/history-model into lp:launchpad

 

Aaron Bentley has proposed merging lp:~abentley/launchpad/history-model into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~abentley/launchpad/history-model/+merge/83310

= Summary =
Change ListingNavigator and BugListingConfigUtil to use the a common model.

== Proposed fix ==
New BugListingModel storing changeable data in Y.History

== Pre-implementation notes ==
Discussed with Deryck

== Implementation details ==
This change unifies the modelling of field visibility in ListingNavigator and BugListingConfigUtil.

ListingNavigator was the sole subscriber to the custom buglisting-config-util:fields-changed event, but was also subscribed to Y.History events.  Now that field changes will trigger Y.History events, the custom event is redundant, and is removed.

BugListingConfigUtil had special handling for the case where field_visibility is not supplied explicitly, but now, that would be used only in test cases, and is removed.  BugListingModel no longer falls back to field_visibility_defaults, so certain tests have different expected values.

== Tests ==
bin/test -t test_buglisting_utils.html -t test_buglisting.html

== Demo and Q/A ==
Go to a +bugs page, e.g. https://bugs.qastaging.launchpad.net/nova/+bugs
Click the gear icon.  Uncheck "Bug number", click "update".  The bug number should disappear.
Click the back button.  The bug number should reappear.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/javascript/buglisting.js
  lib/lp/bugs/templates/buglisting-default.pt
  lib/lp/bugs/javascript/tests/test_buglisting_utils.html
  lib/lp/bugs/javascript/tests/test_buglisting_utils.js
  lib/lp/bugs/javascript/tests/test_buglisting.js
  lib/lp/bugs/javascript/buglisting_utils.js
-- 
https://code.launchpad.net/~abentley/launchpad/history-model/+merge/83310
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/history-model into lp:launchpad.
=== modified file 'lib/lp/bugs/javascript/buglisting.js'
--- lib/lp/bugs/javascript/buglisting.js	2011-11-18 20:11:11 +0000
+++ lib/lp/bugs/javascript/buglisting.js	2011-11-24 15:30:55 +0000
@@ -19,6 +19,77 @@
 
 /**
  * Constructor.
+ *
+ * This is the model of the current batch, including the ordering, position,
+ * and what fields are visibile.
+ *
+ * These values are stored in the History object, so that the browser
+ * back/next buttons correctly adjust.  The system defaults for field
+ * visibility are fixed, so they are stored directly on the object.
+ *
+ * Accepts a config containing:
+ *  - field_visibility the requested field visibility as an associative array
+ *  - field_visibility_defaults the system defaults for field visibility as an
+ *    associative array.
+ *  - batch_key: A string representing the position and ordering of the
+ *    current batch, as returned by ListingNavigator.get_batch_key
+ */
+namespace.BugListingModel = function(){
+    namespace.BugListingModel.superclass.constructor.apply(this, arguments);
+};
+
+
+namespace.BugListingModel.NAME = 'buglisting-model';
+
+
+namespace.BugListingModel.ATTRS = {
+    field_visibility_defaults: {
+        value: null
+    }
+};
+
+
+Y.extend(namespace.BugListingModel, Y.Base, {
+    /**
+     * Initializer sets up the History object that stores most of the model
+     * data.
+     */
+    initializer: function(config){
+        this.set('history', new Y.History({
+            initialState: Y.merge(
+                config.field_visibility, {batch_key: config.batch_key})
+        }));
+    },
+
+    /**
+     * Return the current field visibility, as an associative array.
+     * Since the history contains field values that are not field-visibility,
+     * use field_visibility_defaults to filter out non-field-visibility
+     * values.
+     */
+    get_field_visibility: function(){
+        var result = this.get('history').get();
+        var key_source = this.get('field_visibility_defaults');
+        Y.each(result, function(value, key){
+            if (!key_source.hasOwnProperty(key)){
+                delete result[key];
+            }
+        });
+        return result;
+    },
+
+    /**
+     * Set the field visibility, updating history.  Accepts an associative
+     * array.
+     */
+    set_field_visibility: function(value){
+        this.get('history').add(value);
+    }
+});
+
+
+/**
+ * Constructor.
  * current_url is used to determine search params.
  * cache is the JSONRequestCache for the batch.
  * template is the template to use for rendering batches.
@@ -44,7 +115,6 @@
     navigation_indices: {valueFn: empty_nodelist}
 };
 
-
 Y.extend(namespace.ListingNavigator, Y.Base, {
     initializer: function(config) {
         var lp_client = new Y.lp.client.Launchpad();
@@ -62,9 +132,12 @@
             Y.lp.app.errors.display_error, window, null);
         this.set(
             'failure_handler', this.get('error_handler').getFailureHandler());
-        this.set('field_visibility', cache.field_visibility);
         batch_key = this.handle_new_batch(cache);
-        this.set('current_batch', cache);
+        this.set('model', new namespace.BugListingModel({
+                batch_key: batch_key,
+                field_visibility: cache.field_visibility,
+                field_visibility_defaults: cache.field_visibility_defaults
+        }));
         this.pre_fetch_batches();
         // Work around mustache.js bug 48 "Blank lines are not preserved."
         // https://github.com/janl/mustache.js/issues/48
@@ -76,12 +149,8 @@
         if (Y.Lang.isValue(config.navigation_indices)) {
             this.set('navigation_indices', config.navigation_indices);
         }
-        this.set('history', new Y.History({
-            initialState: {
-                batch_key: batch_key
-            }
-        }));
-        this.get('history').on('change', this.history_changed, this);
+        this.get('model').get('history').after(
+            'change', this.history_changed, this);
     },
 
     /**
@@ -91,7 +160,6 @@
         if (e.newVal.hasOwnProperty('batch_key')) {
             var batch_key = e.newVal.batch_key;
             var batch = this.get('batches')[batch_key];
-            this.set('current_batch', batch);
             this.pre_fetch_batches();
             this.render();
         }
@@ -112,6 +180,14 @@
     },
 
     /**
+     * Retrieve the current batch for rendering purposes.
+     */
+    get_current_batch: function(){
+        var batch_key = this.get('model').get('history').get('batch_key');
+        return this.get('batches')[batch_key];
+    },
+
+    /**
      * Handle a previously-unseen batch by storing it in the cache and
      * stripping out field_visibility values that would otherwise shadow the
      * real values.
@@ -119,7 +195,7 @@
      handle_new_batch: function(batch) {
         var key, i;
         var batch_key = this.constructor.get_batch_key(batch);
-        Y.each(this.get('field_visibility'), function(value, key) {
+        Y.each(batch.field_visibility, function(value, key) {
             for (i = 0; i < batch.mustache_model.bugtasks.length; i++) {
                 delete batch.mustache_model.bugtasks[i][key];
             }
@@ -137,14 +213,15 @@
      * The template is always LP.mustache_listings.
      */
     render: function() {
+        var current_batch = this.get_current_batch();
         var model = Y.merge(
-            this.get('current_batch').mustache_model,
-            this.get('field_visibility'));
+            current_batch.mustache_model,
+            this.get('model').get_field_visibility());
         var batch_info = Mustache.to_html(this.get('batch_info_template'), {
-            start: this.get('current_batch').start + 1,
-            end: this.get('current_batch').start +
-                this.get('current_batch').mustache_model.bugtasks.length,
-            total: this.get('current_batch').total
+            start: current_batch.start + 1,
+            end: current_batch.start +
+                current_batch.mustache_model.bugtasks.length,
+            total: current_batch.total
         });
         var content = Mustache.to_html(this.get('template'), model);
         this.get('target').setContent(content);
@@ -153,11 +230,11 @@
     },
 
     has_prev: function(){
-        return !Y.Lang.isNull(this.get('current_batch').prev);
+        return !Y.Lang.isNull(this.get_current_batch().prev);
     },
 
     has_next: function(){
-        return !Y.Lang.isNull(this.get('current_batch').next);
+        return !Y.Lang.isNull(this.get_current_batch().next);
     },
 
     /**
@@ -187,7 +264,8 @@
      */
     update_from_cache: function(query, batch_key) {
         var url = '?' + Y.QueryString.stringify(query);
-        this.get('history').addValue('batch_key', batch_key, {url: url});
+        this.get('model').get('history').addValue(
+            'batch_key', batch_key, {url: url});
     },
 
     /**
@@ -250,17 +328,18 @@
      * Update the navigator to display the last batch.
      */
     last_batch: function() {
+        var current_batch = this.get_current_batch();
         this.update({
             forwards: false,
             memo: "",
-            start: this.get('current_batch').last_start,
-            order_by: this.get('current_batch').order_by
+            start: current_batch.last_start,
+            order_by: current_batch.order_by
         });
     },
 
     first_batch_config: function(order_by){
         if (order_by === undefined) {
-            order_by = this.get('current_batch').order_by;
+            order_by = this.get_current_batch().order_by;
         }
         return {
             forwards: true,
@@ -280,7 +359,7 @@
     },
 
     next_batch_config: function(){
-        var current_batch = this.get('current_batch');
+        var current_batch = this.get_current_batch();
         if (!this.has_next()){
             return null;
         }
@@ -302,7 +381,7 @@
         this.update(config);
     },
     prev_batch_config: function(){
-        var current_batch = this.get('current_batch');
+        var current_batch = this.get_current_batch();
         if (!this.has_prev()){
             return null;
         }
@@ -324,16 +403,6 @@
         this.update(config);
     },
     /**
-     * Change which fields are displayed in the batch.  Input is a config with
-     * the appropriate visibility variables, such as show_bug_heat,
-     * show_title, etc.
-     */
-    change_fields: function(config) {
-        this.set('field_visibility', Y.merge(this.field_visibility, config));
-        this.render();
-    },
-
-    /**
      * Generate a list of configs to pre-fetch.
      */
     get_pre_fetch_configs: function(){
@@ -359,7 +428,7 @@
                 failure: this.get('failure_handler')
             }
         };
-        var context = this.get('current_batch').context;
+        var context = this.get_current_batch().context;
         if (Y.Lang.isValue(this.get('io_provider'))) {
             load_model_config.io_provider = this.get('io_provider');
         }

=== modified file 'lib/lp/bugs/javascript/buglisting_utils.js'
--- lib/lp/bugs/javascript/buglisting_utils.js	2011-11-22 20:17:09 +0000
+++ lib/lp/bugs/javascript/buglisting_utils.js	2011-11-24 15:30:55 +0000
@@ -2,7 +2,7 @@
 
 YUI().add('lp.buglisting_utils', function(Y) {
     /**
-     * A utiltiy for configuring the display of bug listings.
+     * A utility 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
@@ -18,13 +18,22 @@
      */
 
     // Constants.
-    var FORM = 'form',
-        FIELD_VISIBILITY = 'field_visibility';
+    var FORM = 'form';
+
 
     /**
      * BugListingConfigUtil is the main object used to manipulate
      * a bug listing's display.
      *
+     * Constructor accepts a config containing
+     * - model (a BugListingModel)
+     * - cookie_name
+     * - form the FormOverlay to manipulate
+     *
+     * If model is not supplied, model parameters must be included, especially
+     * - form_visibility
+     * - form_visibility_defaults
+     *
      * @class BugListingConfigUtil
      * @extends Y.lp.configutils.BaseConfigUtil
      * @constructor
@@ -57,51 +66,6 @@
     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.get('field_visibility_defaults');
-            },
-            setter: function(value) {
-                var defaults = this.get('field_visibility_defaults');
-                return Y.merge(defaults, value);
-            }
-        },
-
-        /**
-         * Defaults from field_visibility which are taken from LP.cache.
-         *
-         * This utility will error if LP.cache doesn't exist or doesn't
-         * have field_visibility defined.
-         *
-         * @attribute field_visibility_defaults
-         * @type Object
-         * @default null
-         * @readOnly
-         */
-        field_visibility_defaults: {
-            valueFn: function() {
-                if (
-                    Y.Lang.isValue(window.LP) &&
-                    Y.Lang.isValue(LP.cache.field_visibility_defaults)) {
-                    return LP.cache.field_visibility_defaults;
-                } else {
-                    var msg = [
-                        'LP.cache.field_visibility must be defined ',
-                        'when using BugListingConfigUtil.'
-                    ].join('');
-                    Y.error(msg);
-                }
-            },
-            readOnly: true
-        },
-
-        /**
          * The cookie name as set by the view.
          *
          * We get this value from the LP cache.
@@ -130,6 +94,9 @@
          */
         form: {
             value: null
+        },
+        model: {
+            value: null
         }
     };
 
@@ -139,6 +106,16 @@
 
     Y.extend(BugListingConfigUtil, Y.lp.configutils.BaseConfigUtil, {
 
+        initializer: function(config){
+            if (config === undefined){
+                config = {};
+            }
+            if (Y.Lang.isNull(this.get('model'))){
+                this.set('model',
+                    new Y.lp.bugs.buglisting.BugListingModel(config));
+            }
+        },
+
         /**
          * Hook into the destroy lifecyle to ensure the form
          * overlay is destroyed.
@@ -159,7 +136,7 @@
          * @method getFormInputs
          */
         getFormInputs: function() {
-            var fields = this.get(FIELD_VISIBILITY);
+            var fields = this.get('model').get_field_visibility();
             var display_names = this.constructor.field_display_names;
             var nodes = [];
             var item,
@@ -201,7 +178,8 @@
             link.addClass('reset-buglisting');
             link.setContent('Reset to default');
             link.on('click', function(e) {
-                var defaults = this.get('field_visibility_defaults');
+                var model = this.get('model');
+                var defaults = model.get('field_visibility_defaults');
                 this.updateFieldVisibilty(defaults, true);
                 this.setCookie();
             }, this);
@@ -224,29 +202,12 @@
         },
 
         /**
-         * 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);
-            });
-        },
-
-        /**
          * Helper method for updating field_visibility.
          *
          * @method updateFieldVisibilty
          */
         updateFieldVisibilty: function(fields, destroy_form) {
-            this.set(FIELD_VISIBILITY, fields);
+            this.get('model').set_field_visibility(fields);
             var form = this.get(FORM);
             if (Y.Lang.isValue(form)) {
                 form.hide();
@@ -269,7 +230,7 @@
          * @method handleOverlaySubmit
          */
         handleOverlaySubmit: function(data) {
-            var fields = this.get('field_visibility_defaults');
+            var fields = this.get('model').get_field_visibility();
             var member;
             for (member in fields) {
                 if (fields.hasOwnProperty(member)) {
@@ -358,7 +319,6 @@
             util_overlay.get(
                 'boundingBox').addClass(this.getClassName('overlay'));
             this.set(FORM, util_overlay);
-            this.addListeners();
             util_overlay.render();
             util_overlay.hide();
         },
@@ -379,4 +339,7 @@
     var buglisting_utils = Y.namespace('lp.buglisting_utils');
     buglisting_utils.BugListingConfigUtil = BugListingConfigUtil;
 
-}, '0.1', {'requires': ['cookie', 'lp.configutils', 'lazr.formoverlay']});
+}, '0.1', {'requires': [
+    'cookie', 'history', 'lp.configutils', 'lazr.formoverlay',
+    'lp.bugs.buglisting'
+    ]});

=== modified file 'lib/lp/bugs/javascript/tests/test_buglisting.js'
--- lib/lp/bugs/javascript/tests/test_buglisting.js	2011-11-18 17:20:22 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting.js	2011-11-24 15:30:55 +0000
@@ -31,7 +31,7 @@
                 prev: null
             }
         });
-        bugtask = navigator.get('current_batch').mustache_model.bugtasks[0];
+        bugtask = navigator.get_current_batch().mustache_model.bugtasks[0];
         Y.Assert.isFalse(bugtask.hasOwnProperty('show_item'));
     },
     test_cleans_visibility_from_new_batch: function() {
@@ -39,11 +39,12 @@
         var bugtask;
         var target = Y.Node.create('<div id="client-listing"></div>');
         var model = {
-            field_visibility: {show_item: true},
             mustache_model: {bugtasks: []},
             memo: 1,
             next: null,
-            prev: null
+            prev: null,
+            field_visibility: {},
+            field_visibility_defaults: {show_item: true}
         };
         var navigator = new module.ListingNavigator({
             current_url: '',
@@ -56,11 +57,12 @@
                 bugtasks: [{show_item: true}]},
             memo: 2,
             next: null,
-            prev: null
+            prev: null,
+            field_visibility: {show_item: true}
         };
         var query = navigator.get_batch_query(batch);
         navigator.update_from_new_model(query, false, batch);
-        bugtask = navigator.get('current_batch').mustache_model.bugtasks[0];
+        bugtask = navigator.get_current_batch().mustache_model.bugtasks[0];
         Y.Assert.isFalse(bugtask.hasOwnProperty('show_item'));
     }
 }));
@@ -82,7 +84,8 @@
             prev: null,
             start: 5,
             total: 256,
-            field_visibility: {show_foo: true}
+            field_visibility: {show_foo: true},
+            field_visibility_defaults: {show_foo: false}
         };
         var template = "{{#bugtasks}}{{#show_foo}}{{foo}}{{/show_foo}}" +
             "{{/bugtasks}}";
@@ -127,7 +130,7 @@
         var navigator = this.get_render_navigator();
         var action = navigator.get('backwards_navigation').item(0);
         action.addClass('inactive');
-        navigator.get('current_batch').prev = {
+        navigator.get_current_batch().prev = {
             start: 1, memo: 'pi'
         };
         navigator.render_navigation();
@@ -152,7 +155,7 @@
         var navigator = this.get_render_navigator();
         var action = navigator.get('forwards_navigation').item(0);
         action.addClass('inactive');
-        navigator.get('current_batch').next = {
+        navigator.get_current_batch().next = {
             start: 1, memo: 'pi'
         };
         navigator.render_navigation();
@@ -193,16 +196,6 @@
         Y.Assert.areEqual(
             '<strong>6</strong> \u2192 <strong>6</strong> of 256 results',
             index.getContent());
-    },
-    test_change_fields: function() {
-        // change_fields updates field visibility state and re-renders.
-        var navigator = this.get_render_navigator();
-        navigator.render();
-        Y.Assert.areEqual('bar', navigator.get('target').getContent());
-        Y.Assert.isTrue(navigator.get('field_visibility').show_foo);
-        navigator.change_fields({show_foo: false});
-        Y.Assert.isFalse(navigator.get('field_visibility').show_foo);
-        Y.Assert.areEqual('', navigator.get('target').getContent());
     }
 }));
 
@@ -224,7 +217,9 @@
                 bugtasks: []
             },
             next: null,
-            prev: null
+            prev: null,
+            field_visibility: {},
+            field_visibility_defaults: {}
         };
         var target = Y.Node.create('<div id="client-listing"></div>');
         var navigator = new module.ListingNavigator({
@@ -300,7 +295,9 @@
                 foo: 'bar'
             },
             next: null,
-            prev: null
+            prev: null,
+            field_visibility: {},
+            field_visibility_defaults: {}
         };
         var template = "<ol>" +
             "{{#item}}<li>{{name}}</li>{{/item}}</ol>";
@@ -457,7 +454,9 @@
             start: 400
         },
         order_by: 'foo',
-        last_start: 23
+        last_start: 23,
+        field_visibility: {},
+        field_visibility_defaults: {}
     };
     if (config.no_next){
         lp_cache.next = null;
@@ -561,7 +560,7 @@
     test_update_from_cache_generates_event: function(){
         var navigator = get_navigator('');
         var e = null;
-        navigator.get('history').on('change', function(inner_e){
+        navigator.get('model').get('history').on('change', function(inner_e){
             e = inner_e;
         });
         navigator.get('batches')['some-batch-key'] = {
@@ -592,8 +591,9 @@
         };
         navigator.set('template', '{{foo}}');
         navigator.get('batches')['some-batch-key'] = batch;
-        navigator.get('history').addValue('batch_key', 'some-batch-key');
-        Y.Assert.areEqual(batch, navigator.get('current_batch'));
+        navigator.get('model').get('history').addValue(
+            'batch_key', 'some-batch-key');
+        Y.Assert.areEqual(batch, navigator.get_current_batch());
         Y.Assert.areEqual('bar', navigator.get('target').getContent());
     }
 }));
@@ -662,14 +662,9 @@
 
     get_pre_fetch_navigator: function(config){
         var navigator = get_navigator('', config);
-        var lp_client = new Y.lp.client.Launchpad();
-        var batch = lp_client.wrap_resource(null, {
-            context: {
-                resource_type_link: 'http://foo_type',
-                web_link: 'http://foo/bar'
-            },
-            next: {memo: 57, start: 56}});
-        navigator.set('current_batch', batch);
+        var batch = navigator.get_current_batch();
+        batch.next = {memo: 57, start: 56};
+        batch.order_by = '';
         return navigator;
     },
 

=== modified file 'lib/lp/bugs/javascript/tests/test_buglisting_utils.html'
--- lib/lp/bugs/javascript/tests/test_buglisting_utils.html	2011-11-10 11:42:42 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting_utils.html	2011-11-24 15:30:55 +0000
@@ -14,13 +14,24 @@
 
  <!-- Dependencies from our tree -->
   <script type="text/javascript"
+          src="../../../app/javascript/lp.js"></script>
+  <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/client.js"></script>
+  <script type="text/javascript"
+          src="../../../app/javascript/effects/effects.js"></script>
+  <script type="text/javascript"
+          src="../../../app/javascript/errors.js"></script>
+  <script type="text/javascript"
+          src="../../../app/javascript/expander.js"></script>
+  <script type="text/javascript"
           src="../../../app/javascript/overlay/overlay.js"></script>
+  <script type="text/javascript" src="../buglisting.js"></script>
 
   <!-- The module under test -->
   <script type="text/javascript" src="../buglisting_utils.js"></script>

=== modified file 'lib/lp/bugs/javascript/tests/test_buglisting_utils.js'
--- lib/lp/bugs/javascript/tests/test_buglisting_utils.js	2011-11-22 20:10:22 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting_utils.js	2011-11-24 15:30:55 +0000
@@ -14,17 +14,39 @@
 
     name: 'buglisting_display_utils_tests',
 
-    _should: {
-        error: {
-
-            test_widget_requires_cache: [
-                'LP.cache.field_visibility must be defined ',
-                'when using BugListingConfigUtil.'
-            ].join('')
-        }
-    },
-
     setUp: function() {
+        // Default values for model config.
+        this.defaults = {
+            field_visibility: {
+                show_title: true,
+                show_id: false,
+                show_importance: false,
+                show_status: true,
+                show_bug_heat: true,
+                show_bugtarget: true,
+                show_age: false,
+                show_last_updated: false,
+                show_assignee: false,
+                show_reporter: false,
+                show_milestone_name: false,
+                show_tags: false
+            },
+
+            field_visibility_defaults: {
+                show_title: true,
+                show_id: true,
+                show_importance: true,
+                show_status: true,
+                show_bug_heat: true,
+                show_bugtarget: true,
+                show_age: false,
+                show_last_updated: false,
+                show_assignee: false,
+                show_reporter: false,
+                show_milestone_name: false,
+                show_tags: false
+            }
+        };
         // _setDoc is required for tests using cookies to pass.
         Y.Cookie._setDoc({cookie: ""});
         // Simulate LP.cache.field_visibility which will be
@@ -35,39 +57,10 @@
             },
 
             cache: {
-                field_visibility: {
-                    show_title: true,
-                    show_id: false,
-                    show_importance: false,
-                    show_status: true,
-                    show_bug_heat: true,
-                    show_bugtarget: true,
-                    show_age: false,
-                    show_last_updated: false,
-                    show_assignee: false,
-                    show_reporter: false,
-                    show_milestone_name: false,
-                    show_tags: false
-                },
-
-                field_visibility_defaults: {
-                    show_title: true,
-                    show_id: true,
-                    show_importance: true,
-                    show_status: true,
-                    show_bug_heat: true,
-                    show_bugtarget: true,
-                    show_age: false,
-                    show_last_updated: false,
-                    show_assignee: false,
-                    show_reporter: false,
-                    show_milestone_name: false,
-                    show_tags: false
-                },
-
                 cbl_cookie_name: 'foobar-buglist-fields'
             }
         };
+
         this.cookie_name = LP.cache.cbl_cookie_name;
     },
 
@@ -107,23 +100,6 @@
         Assert.isInstanceOf(Y.lp.configutils.BaseConfigUtil, this.list_util);
     },
 
-    test_default_field_visibility_config: function() {
-        // The default field_visibility must be supplied by LP.cache.
-        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
-        var defaults_from_cache = LP.cache.field_visibility_defaults;
-        var field_visibility_defaults = this.list_util.get(
-            'field_visibility_defaults');
-        ObjectAssert.areEqual(defaults_from_cache, field_visibility_defaults);
-    },
-
-    test_widget_requires_cache: function() {
-        // BugListingConfigUtil requires our JSON cache for
-        // field_visibility and will error on render if not found.
-        delete window.LP;
-        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
-        this.list_util.render();
-    },
-
     test_cookie_name_attribute: function() {
         // cookie_name is taken from the cache.
         this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
@@ -139,39 +115,11 @@
             foo: 'bar',
             baz: 'bop'
         };
-        this.list_util.set('field_visibility_defaults', attempted_defaults);
+        this.list_util.get('model').set(
+            'field_visibility_defaults', attempted_defaults);
         ObjectAssert.areEqual(
             this.list_util.get('field_visibility_defaults'),
-            LP.cache.field_visibility_defaults);
-    },
-
-    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_bug_heat: false,
-            show_status: false
-        };
-        var expected_config = {
-            show_title: true,
-            show_id: true,
-            show_importance: true,
-            show_status: false,
-            show_bug_heat: false,
-            show_bugtarget: true,
-            show_age: false,
-            show_last_updated: false,
-            show_assignee: false,
-            show_reporter: false,
-            show_milestone_name: false,
-            show_tags: false
-        };
-        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
-            field_visibility: supplied_config
-        });
-        this.list_util.render();
-        ObjectAssert.areEqual(
-            expected_config, this.list_util.get('field_visibility'));
+            this.defaults.field_visibility_defaults);
     },
 
     test_cookie_updates_field_visibility_config: function() {
@@ -192,9 +140,11 @@
             show_tags: false
         };
         Y.Cookie.setSubs(this.cookie_name, expected_config);
-        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil(
+            this.defaults);
         this.list_util.render();
-        var actual_config = this.list_util.get('field_visibility');
+        var model = this.list_util.get('model');
+        var actual_config = model.get_field_visibility();
         ObjectAssert.areEqual(expected_config, actual_config);
     },
 
@@ -205,10 +155,11 @@
         Assert.isNotUndefined(this.list_util.get('form'));
     },
 
-    test_field_visibility_form_shows_defaults: function() {
-        // The form should have a checkbox for every default item,
+    test_field_visibility_form_shows_initial: function() {
+        // The form should have a checkbox for every field_visibility item,
         // and the checked value should match true or false values.
-        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil(
+            this.defaults);
         this.list_util.render();
         var expected_names = [
             'show_title',
@@ -226,8 +177,8 @@
         ];
         var expected_checked = [
             true,
-            true,
-            true,
+            false,
+            false,
             true,
             true,
             true,
@@ -246,12 +197,14 @@
     test_field_visibility_form_shows_supplied_defaults: function() {
         // The form checkboxes should also match the user supplied
         // config values.
-        var field_visibility = {
+        var field_visibility = Y.merge(
+            this.defaults.field_visibility_defaults, {
             show_status: false,
             show_bug_heat: false
-        };
+        });
         this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
-            field_visibility: field_visibility
+            field_visibility: field_visibility,
+            field_visibility_defaults: this.defaults.field_visibility_defaults
         });
         this.list_util.render();
         var expected_names = [
@@ -289,7 +242,8 @@
 
     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 = new Y.lp.buglisting_utils.BugListingConfigUtil(
+            this.defaults);
         this.list_util.render();
         var overlay = this.list_util.get('form').get('boundingBox');
         Assert.isTrue(overlay.hasClass('yui3-lazr-formoverlay-hidden'));
@@ -301,7 +255,8 @@
     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 = new Y.lp.buglisting_utils.BugListingConfigUtil(
+            this.defaults);
         this.list_util.render();
         var config = Y.one('.config');
         config.simulate('click');
@@ -313,8 +268,8 @@
         update.simulate('click');
         var expected_config = {
             show_title: true,
-            show_id: true,
-            show_importance: true,
+            show_id: false,
+            show_importance: false,
             show_status: true,
             show_bug_heat: false,
             show_bugtarget: false,
@@ -325,13 +280,15 @@
             show_milestone_name: false,
             show_tags: false
         };
-        var actual_config = this.list_util.get('field_visibility');
+        var model = this.list_util.get('model');
+        var actual_config = model.get_field_visibility();
         ObjectAssert.areEqual(expected_config, actual_config);
     },
 
     test_form_update_hides_overlay: function() {
         // Updating the form overlay hides the overlay.
-        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil(
+            this.defaults);
         this.list_util.render();
         var config = Y.one('.config');
         config.simulate('click');
@@ -343,31 +300,11 @@
         Assert.isTrue(overlay.hasClass('yui3-lazr-formoverlay-hidden'));
     },
 
-    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_update_from_form_updates_cookie: function() {
         // When the form is submitted, a cookie is set to match
         // your preferred field_visibility.
-        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil(
+            this.defaults);
         this.list_util.render();
         // Now poke at the page to set the cookie.
         var config = Y.one('.config');
@@ -378,8 +315,8 @@
         update.simulate('click');
         var expected_config = {
             show_title: true,
-            show_id: true,
-            show_importance: true,
+            show_id: false,
+            show_importance: false,
             show_status: true,
             show_bug_heat: true,
             show_bugtarget: false,
@@ -404,23 +341,18 @@
             show_bug_heat: false
         };
         this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
-            field_visibility: field_visibility
+            field_visibility: field_visibility,
+            field_visibility_defaults: this.defaults.field_visibility_defaults
         });
         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);
+        var model = this.list_util.get('model');
+        var defaults = model.get('field_visibility_defaults');
+        var fields = model.get_field_visibility();
+        ObjectAssert.areEqual(defaults, fields);
     },
 
     test_fields_visibility_form_reset_hides_overlay: function() {
@@ -430,7 +362,8 @@
             show_bug_heat: false
         };
         this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
-            field_visibility: field_visibility
+            field_visibility: field_visibility,
+            field_visibility_defaults: this.defaults.field_visibility_defaults
         });
         this.list_util.render();
         // Poke at the form to reset defaults.
@@ -443,12 +376,14 @@
 
     test_fields_visibility_form_reset_updates_form: function() {
         // Reseting to defaults should reset the form inputs, too.
-        var field_visibility = {
+        var field_visibility = Y.merge(
+            this.defaults.field_visibility_defaults, {
             show_bugtarget: false,
             show_bug_heat: false
-        };
+        });
         this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
-            field_visibility: field_visibility
+            field_visibility: field_visibility,
+            field_visibility_defaults: this.defaults.field_visibility_defaults
         });
         this.list_util.render();
         var expected_names = [
@@ -491,7 +426,8 @@
     test_form_reset_removes_cookie: function() {
         // Clicking "reset to defaults" on the overlay will
         // remove any cookie added.
-        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil();
+        this.list_util = new Y.lp.buglisting_utils.BugListingConfigUtil(
+            this.defaults);
         this.list_util.render();
         // Now poke at the page to set the cookie.
         var config = Y.one('.config');

=== modified file 'lib/lp/bugs/templates/buglisting-default.pt'
--- lib/lp/bugs/templates/buglisting-default.pt	2011-11-10 21:58:53 +0000
+++ lib/lp/bugs/templates/buglisting-default.pt	2011-11-24 15:30:55 +0000
@@ -53,12 +53,10 @@
             });
             var config_node = orderby.get('config_node');
             var list_util = new Y.lp.buglisting_utils.BugListingConfigUtil({
-                srcNode: config_node
+                srcNode: config_node,
+                model: navigator.get('model')
             });
             list_util.render();
-            Y.on('buglisting-config-util:fields-changed', function(e) {
-                navigator.change_fields(list_util.get('field_visibility'));
-            });
         });
     });
   </script>