← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~huwshimi/maas/morphing-integration into lp:maas

 

Huw Wilkins has proposed merging lp:~huwshimi/maas/morphing-integration into lp:maas.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~huwshimi/maas/morphing-integration/+merge/100361

This branch converts the add node overlay to be a generic widget and use morphing to show/hide the widget in place.
-- 
https://code.launchpad.net/~huwshimi/maas/morphing-integration/+merge/100361
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~huwshimi/maas/morphing-integration into lp:maas.
=== added file 'src/maasserver/static/css/components/yui_node_add.css'
--- src/maasserver/static/css/components/yui_node_add.css	1970-01-01 00:00:00 +0000
+++ src/maasserver/static/css/components/yui_node_add.css	2012-04-02 06:48:18 +0000
@@ -0,0 +1,6 @@
+.yui3-node-add-widget {
+    width: 360px;
+    }
+.yui3-node-add-widget .buttons {
+    margin-top: 30px;
+    }

=== modified file 'src/maasserver/static/css/forms.css'
--- src/maasserver/static/css/forms.css	2012-03-26 05:17:47 +0000
+++ src/maasserver/static/css/forms.css	2012-04-02 06:48:18 +0000
@@ -127,6 +127,10 @@
     background-image: -webkit-linear-gradient(bottom, rgb(51,51,51) 0%, rgb(90,90,90) 100%);
     background-image: -ms-linear-gradient(bottom, rgb(51,51,51) 0%, rgb(90,90,90) 100%);
     }
+.link-button {
+    display: inline-block;
+    padding: 6px 0;
+    }
 .spinner {
     float: right;
     margin: 8px 10px 0 0;

=== modified file 'src/maasserver/static/css/import.css'
--- src/maasserver/static/css/import.css	2012-03-01 06:22:38 +0000
+++ src/maasserver/static/css/import.css	2012-04-02 06:48:18 +0000
@@ -11,5 +11,6 @@
 @import url("components/blocks.css");
 @import url("components/yui_panel.css");
 @import url("components/yui_overlay.css");
+@import url("components/yui_node_add.css");
 @import url("components/data_list.css");
 @import url("components/search_box.css");

=== modified file 'src/maasserver/static/js/morph.js'
--- src/maasserver/static/js/morph.js	2012-03-19 01:14:42 +0000
+++ src/maasserver/static/js/morph.js	2012-04-02 06:48:18 +0000
@@ -43,15 +43,14 @@
             var srcNode = this.get('srcNode');
             var targetNode = this.get('targetNode');
         }
-        
-        target_height = targetNode.getComputedStyle('height');
+        var target_height = targetNode.getComputedStyle('height');
         var fade_out = new Y.Anim({
             node: targetNode,
             to: {opacity: 0},
             duration: 0.2,
             easing: 'easeOut'
             });
-        fade_out.run();
+        var self = this;
         fade_out.on('end', function () {
             targetNode.addClass('hidden');
             srcNode.setStyle('opacity', 0);
@@ -70,9 +69,14 @@
                 duration: 0.5,
                 easing: 'easeOut'
                 });
+            resize.on('end', function () {
+                srcNode.setStyle('height', 'auto');
+                self.fire('morphed');
+            });
             fade_in.run();
             resize.run();
         });
+        fade_out.run();
     }
 });
 

=== modified file 'src/maasserver/static/js/node_add.js'
--- src/maasserver/static/js/node_add.js	2012-03-26 05:27:45 +0000
+++ src/maasserver/static/js/node_add.js	2012-04-02 06:48:18 +0000
@@ -36,10 +36,30 @@
             return this.get(
                 'srcNode').all('input[name=mac_addresses]').size();
         }
+    },
+
+    /**
+     * The DOM node to be morphed from.
+     *
+     * @attribute targetNode
+     * @type string
+     */
+    targetNode: {
+        value: null
+    },
+
+   /**
+    * Set the panel to fade in/out or just appear/disappear
+    *
+    * @attribute animate
+    * @type boolean
+    */
+    animate: {
+        value: true
     }
 };
 
-Y.extend(AddNodeWidget, Y.Panel, {
+Y.extend(AddNodeWidget, Y.Widget, {
 
     /**
      * Create an input field to add a MAC Address.
@@ -55,23 +75,6 @@
         return Y.Node.create('<p />').append(field);
     },
 
-    /**
-     * Hide the panel.
-     *
-     * @method hidePanel
-     */
-    hidePanel: function() {
-        var self = this;
-        this.get('boundingBox').transition({
-            duration: 0.5,
-            top: '-400px'
-        },
-        function () {
-            self.hide();
-            self.destroy();
-        });
-    },
-
     addMacField: function() {
         if (this.get('nb_mac_fields') === 1) {
             var label = this.get(
@@ -112,6 +115,15 @@
     },
 
     createForm: function() {
+        var addnode_button = Y.Node.create('<button />')
+            .addClass('add-node-button')
+            .addClass('right')
+            .set('text', "Add node");
+        var cancel_button = Y.Node.create('<a />')
+            .addClass('cancel-button')
+            .set('href', '#')
+            .set('text', "Cancel")
+            .addClass('link-button');
         var macaddress_add_link = Y.Node.create('<a />')
             .addClass('add-link')
             .addClass('add-mac-form')
@@ -123,6 +135,10 @@
             .set('value', 'new');
         var global_error = Y.Node.create('<p />')
             .addClass('form-errors');
+        var buttons = Y.Node.create('<div />')
+            .addClass('buttons')
+            .append(addnode_button)
+            .append(cancel_button);
         var addnodeform = Y.Node.create('<form />')
             .set('method', 'post')
             .append(global_error)
@@ -130,7 +146,8 @@
             .append(Y.Node.create(this.add_macaddress))
             .append(macaddress_add_link)
             .append(Y.Node.create(this.add_architecture))
-            .append(Y.Node.create(this.add_node));
+            .append(Y.Node.create(this.add_node))
+            .append(buttons);
         return addnodeform;
     },
 
@@ -162,8 +179,8 @@
     * @method showSpinner
     */
     showSpinner: function() {
-        var buttons = this.get('srcNode').one('.yui3-widget-button-wrapper');
-        buttons.append(this.spinnerNode);
+        var buttons = this.get('srcNode').one('.add-node-button');
+        buttons.insert(this.spinnerNode, 'after');
     },
 
     /**
@@ -176,16 +193,60 @@
     },
 
     initializer: function(cfg) {
+        this.get('srcNode').addClass('hidden');
+        this.morpher = new Y.maas.morph.Morph(
+            {srcNode: cfg.srcNode, targetNode: this.get('targetNode')});
+    },
+
+    renderUI: function() {
         // Load form snippets.
         this.add_macaddress = Y.one('#add-macaddress').getContent();
         this.add_architecture = Y.one('#add-architecture').getContent();
         this.add_node = Y.one('#add-node').getContent();
         // Create panel's content.
-        this.set('bodyContent', this.createForm());
+        var heading = Y.Node.create('<h2 />')
+            .set('text', "Add node");
+        this.get('srcNode').append(heading).append(this.createForm());
         this.initializeNodes();
     },
 
     /**
+     * Show the widget
+     *
+     * @method showWidget
+     */
+    showWidget: function() {
+        if (this.get('animate')) {
+            this.morpher.morph();
+            this.morpher.on('morphed', function(e, widget) {
+                widget.get('srcNode').one('input[type=text]').focus();
+            }, null, this);
+        }
+        else {
+            Y.one(this.get('targetNode')).addClass('hidden');
+            this.get('srcNode').removeClass('hidden');
+        }
+    },
+
+    /**
+     * Hide the widget
+     *
+     * @method showWidget
+     */
+    hideWidget: function() {
+        if (this.get('animate')) {
+            this.morpher.morph('reverse');
+            this.morpher.on('morphed', function(e, widget) {
+                widget.destroy();
+            }, null, this);
+        }
+        else {
+            this.get('srcNode').addClass('hidden');
+            Y.one(this.get('targetNode')).removeClass('hidden');
+        }
+    },
+
+    /**
      * Initialize the nodes this widget will use.
      *
      * @method initializeNodes
@@ -207,14 +268,22 @@
 
     bindUI: function() {
         var self = this;
-        this.get(
-            'bodyContent').one('.add-mac-form').on('click', function(e) {
+        var srcNode = this.get('srcNode');
+        srcNode.one('.add-mac-form').on('click', function(e) {
             e.preventDefault();
             self.addMacField();
         });
-        this.get('bodyContent').on('key', function() {
+        srcNode.on('key', function() {
             self.sendAddNodeRequest();
         }, 'press:enter');
+        srcNode.one('.add-node-button').on('click', function(e) {
+            e.preventDefault();
+            self.sendAddNodeRequest();
+        });
+        srcNode.one('.cancel-button').on('click', function(e, widget) {
+            e.preventDefault();
+            widget.hideWidget();
+        }, null, this);
     },
 
     addNode: function(node) {
@@ -231,7 +300,7 @@
                 start:  Y.bind(self.showSpinner, self),
                 success: function(id, out) {
                     self.addNode(JSON.parse(out.response));
-                    self.hidePanel();
+                    self.hideWidget();
                 },
                 failure: function(id, out) {
                     Y.log("Adding a node failed.  Response object follows.")
@@ -297,11 +366,7 @@
  *
  * @method showAddNodeWidget
  */
-module.showAddNodeWidget = function(event) {
-    // Cope with manual calls as well as event calls.
-    if (Y.Lang.isValue(event)) {
-        event.preventDefault();
-    }
+module.showAddNodeWidget = function(cfg) {
     // If a widget is already present, destroy it.
     var destroy = (
         Y.Lang.isValue(module._add_node_singleton) &&
@@ -309,48 +374,17 @@
     if (destroy) {
         module._add_node_singleton.destroy();
     }
-    var cfg = {
-        headerContent: "Add node",
-        buttons: [
-            {
-                value: 'Add node',
-                section: 'footer',
-                action: function (e) {
-                    e.preventDefault();
-                    this.sendAddNodeRequest();
-                }
-            },
-            {
-                value: 'Cancel',
-                section: 'footer',
-                classNames: 'link-button',
-                action: function (e) {
-                    e.preventDefault();
-                    this.hidePanel();
-                }
-            }],
-        align: {
-            node:'',
-            points:
-                [Y.WidgetPositionAlign.BC, Y.WidgetPositionAlign.TC]
-            },
-        modal: true,
-        zIndex: 2,
-        visible: true,
-        render: true,
-        hideOn: []
-        };
+
+    var add_node_id = 'add-node-widget';
+    cfg.srcNode = '#' + add_node_id
+    var srcNode = Y.Node.create('<div />')
+        .set('id', add_node_id);
+    Y.one(cfg.targetNode).insert(srcNode, 'after');
     module._add_node_singleton = new AddNodeWidget(cfg);
-    module._add_node_singleton.get('boundingBox').transition({
-        duration: 0.5,
-        top: '0px'
-    });
-    // We need to set the focus late as the widget wants to set the focus
-    // on the bounding box.
-    module._add_node_singleton.get(
-        'boundingBox').one('input[type=text]').focus();
+    module._add_node_singleton.render();
+    module._add_node_singleton.showWidget();
 };
 
-}, '0.1', {'requires': ['io', 'node', 'panel', 'event', 'event-custom',
-                        'transition']}
+}, '0.1', {'requires': ['io', 'node', 'widget', 'event', 'event-custom',
+                        'maas.morph']}
 );

=== modified file 'src/maasserver/static/js/tests/test_morph.js'
--- src/maasserver/static/js/tests/test_morph.js	2012-03-19 01:14:42 +0000
+++ src/maasserver/static/js/tests/test_morph.js	2012-04-02 06:48:18 +0000
@@ -25,6 +25,10 @@
         Y.Assert.isTrue(
             Y.one('#panel-two').hasClass('hidden'),
             'The source panel should initially be hidden');
+        var morphed_fired = false;
+        morpher.on('morphed', function() {
+            morphed_fired = true;
+        });
         morpher.morph();
         this.wait(function() {
             Y.Assert.isTrue(
@@ -33,6 +37,13 @@
             Y.Assert.isFalse(
                 Y.one(cfg.srcNode).hasClass('hidden'),
                 'The source panel should now be visible');
+            Y.Assert.isTrue(
+                morphed_fired,
+                'The morphed event should have fired');
+            Y.Assert.areEqual(
+                'auto',
+                Y.one(cfg.srcNode).getStyle('height'),
+                'The morpher should set the height back to auto');
             /* Fire this morph again, this time for the reverse. */
             morpher.morph(true);
             this.wait(function() {

=== modified file 'src/maasserver/static/js/tests/test_node_add.html'
--- src/maasserver/static/js/tests/test_node_add.html	2012-03-15 13:58:32 +0000
+++ src/maasserver/static/js/tests/test_node_add.html	2012-04-02 06:48:18 +0000
@@ -8,6 +8,7 @@
     <script type="text/javascript" src="../../jslibs/yui/tests/build/yui/yui.js"></script>
     <script type="text/javascript" src="../testing/testrunner.js"></script>
     <script type="text/javascript" src="../testing/testing.js"></script>
+    <script type="text/javascript" src="../morph.js"></script>
     <!-- The module under test -->
     <script type="text/javascript" src="../node_add.js"></script>
     <!-- The test suite -->
@@ -47,7 +48,7 @@
     <input type="text" maxlength="30" name="hostname" id="id_hostname" />
     </p>
   </script>
-
+  <div id="target_node"></div>
   <span id="suite">maas.node_add.tests</span>
   </body>
 </html>

=== modified file 'src/maasserver/static/js/tests/test_node_add.js'
--- src/maasserver/static/js/tests/test_node_add.js	2012-03-27 06:50:37 +0000
+++ src/maasserver/static/js/tests/test_node_add.js	2012-04-02 06:48:18 +0000
@@ -26,14 +26,14 @@
     testSingletonCreation: function() {
         // module._add_node_singleton is originally null.
         Y.Assert.isNull(module._add_node_singleton);
-        module.showAddNodeWidget();
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
         // module._add_node_singleton is populated after the call to
         // module.showAddNodeWidget.
         Y.Assert.isNotNull(module._add_node_singleton);
     },
 
     testSingletonReCreation: function() {
-        module.showAddNodeWidget();
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
         var panel = module._add_node_singleton;
 
         // Make sure that a second call to showAddNodeWidget destroys
@@ -42,7 +42,7 @@
         panel.on("destroy", function(){
             destroyed = true;
         });
-        module.showAddNodeWidget();
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
         Y.Assert.isTrue(destroyed);
         Y.Assert.isNotNull(module._add_node_singleton);
         Y.Assert.areNotSame(panel, namespace._add_node_singleton);
@@ -75,7 +75,7 @@
 /* Find the "Add node" button at the bottom of the add-node form.
  */
 function find_add_button() {
-    return find_widget().one('.yui3-button');
+    return find_widget().one('.add-node-button');
 }
 
 
@@ -89,7 +89,7 @@
 /* Set up and submit the add-node form.
  */
 function submit_add_node() {
-    module.showAddNodeWidget();
+    module.showAddNodeWidget({targetNode: '#target_node', animate: false});
     find_hostname_input().set('value', 'host');
     find_add_button().simulate('click');
 }
@@ -100,7 +100,7 @@
 
     testFormContainsArchitectureChoice: function() {
         // The generated form contains an 'architecture' field.
-        module.showAddNodeWidget();
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
         var arch = find_form().one('#id_architecture');
         Y.Assert.isNotNull(arch);
         var arch_options = arch.all('option');
@@ -110,7 +110,7 @@
     testAddNodeAPICallSubmitsForm: function() {
         // The call to the API triggered by clicking on 'Add a node'
         // submits (via an API call) the panel's form.
-        module.showAddNodeWidget();
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
         var mockXhr = new Y.Base();
         var form = find_form();
         var log = this.logIO(module);
@@ -126,7 +126,7 @@
             args: [MAAS_config.uris.nodes_handler, Y.Mock.Value.Any]
         });
         this.mockIO(mockXhr, module);
-        module.showAddNodeWidget();
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
         find_hostname_input().set('value', 'host');
         find_add_button().simulate('click');
         Y.Mock.verify(mockXhr);
@@ -139,7 +139,7 @@
             args: [MAAS_config.uris.nodes_handler, Y.Mock.Value.Any]
         });
         this.mockIO(mockXhr, module);
-        module.showAddNodeWidget();
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
         find_hostname_input().set('value', 'host');
         // Simulate 'Enter' being pressed.
         find_form().simulate("keypress", { keyCode: 13 });
@@ -148,7 +148,7 @@
 
     testNodeidPopulation: function() {
         this.mockSuccess(Y.JSON.stringify({system_id: 3}), module);
-        module.showAddNodeWidget();
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
         this.addCleanup(
             Y.bind(
                 module._add_node_singleton.destroy,

=== modified file 'src/maasserver/templates/maasserver/index.html'
--- src/maasserver/templates/maasserver/index.html	2012-03-20 05:59:00 +0000
+++ src/maasserver/templates/maasserver/index.html	2012-04-02 06:48:18 +0000
@@ -14,7 +14,7 @@
   <!--
   YUI().use(
     'maas.node_add', 'maas.node','maas.node_views', 'maas.utils',
-    'maas.longpoll', 
+    'maas.longpoll',
     function (Y) {
     Y.on('load', function() {
       // Create Dashboard view.
@@ -26,7 +26,11 @@
           reservedNode: '#reserved-nodes',
           retiredNode: '#retired-nodes'});
       view.render();
-      Y.one('#addnode').on('click', Y.maas.node_add.showAddNodeWidget);
+      
+      Y.one('#addnode').on('click', function(e) {
+        e.preventDefault();
+        Y.maas.node_add.showAddNodeWidget({targetNode: '#dashboard'});
+      });
 
       // Setup TitleEditWidget.
       var title_widget = new Y.maas.utils.TitleEditWidget(
@@ -70,5 +74,6 @@
       <p id="retired-nodes" class="secondary medium space-top-none"></p>
       <a href="#" id="addnode" class="button right space-top">Add node</a>
     </div>
+    <div class="clear"></div>
   </div>
 {% endblock %}

=== modified file 'src/maasserver/templates/maasserver/node_list.html'
--- src/maasserver/templates/maasserver/node_list.html	2012-03-22 06:38:06 +0000
+++ src/maasserver/templates/maasserver/node_list.html	2012-04-02 06:48:18 +0000
@@ -14,7 +14,10 @@
   <!--
   YUI().use('maas.node_add', function (Y) {
     Y.on('load', function() {
-      Y.one('#addnode').on('click', Y.maas.node_add.showAddNodeWidget);
+      Y.one('#addnode').on('click', function(e) {
+        e.preventDefault();
+        Y.maas.node_add.showAddNodeWidget({targetNode: '#nodes'});
+      });
     });
   });
   // -->
@@ -22,13 +25,14 @@
 {% endblock %}
 
 {% block content %}
-  {% comment %}
-    <!-- To be enabled when we have search functionality -->
-    <form action="" method="get" class="block full-width inline-form">
-      <input type="text" name="query" class="search-box" value="Search nodes" />
-    </form>
-  {% endcomment %}
-  {% if node_list|length %}
+  <div id="nodes">
+    {% comment %}
+      <!-- To be enabled when we have search functionality -->
+      <form action="" method="get" class="block full-width inline-form">
+        <input type="text" name="query" class="search-box" value="Search nodes" />
+      </form>
+    {% endcomment %}
+    {% if node_list|length %}
       <table class="list">
         <thead>
           <tr>
@@ -50,6 +54,7 @@
           </tr>
         {% endfor %}
       </table>
-  {% endif%}
-  <a id="addnode" href="#" class="button right space-top">Add node</a>
+    {% endif%}
+    <a id="addnode" href="#" class="button right space-top">Add node</a>
+  </div>
 {% endblock %}