From f999881008dff44a0292482092cdd2a8b6509d88 Mon Sep 17 00:00:00 2001
From: Shane Tomlinson <stomlinson@mozilla.com>
Date: Mon, 20 Aug 2012 10:33:26 +0100
Subject: [PATCH] Split up the PageModule into Module, DOMModule and
 PageModule.

* Increase coherency for each of the three modules
---
 lib/static_resources.js                       |   2 +
 .../static/common/js/modules/dom_module.js    |  83 +++++++++++++
 resources/static/common/js/modules/module.js  |  80 ++++++++++++
 .../static/common/js/modules/page_module.js   | 114 +++---------------
 .../cases/common/js/modules/dom_module.js     |  75 ++++++++++++
 .../test/cases/common/js/modules/module.js    |  95 +++++++++++++++
 .../cases/common/js/modules/page_module.js    |  85 -------------
 resources/views/test.ejs                      |   5 +-
 8 files changed, 353 insertions(+), 186 deletions(-)
 create mode 100644 resources/static/common/js/modules/dom_module.js
 create mode 100644 resources/static/common/js/modules/module.js
 create mode 100644 resources/static/test/cases/common/js/modules/dom_module.js
 create mode 100644 resources/static/test/cases/common/js/modules/module.js

diff --git a/lib/static_resources.js b/lib/static_resources.js
index 1a1845561..72d9ea339 100644
--- a/lib/static_resources.js
+++ b/lib/static_resources.js
@@ -51,6 +51,8 @@ var common_js = [
   '/common/js/network.js',
   '/common/js/provisioning.js',
   '/common/js/user.js',
+  '/common/js/modules/module.js',
+  '/common/js/modules/dom_module.js',
   '/common/js/modules/page_module.js',
   '/common/js/modules/xhr_delay.js',
   '/common/js/modules/xhr_disable_form.js',
diff --git a/resources/static/common/js/modules/dom_module.js b/resources/static/common/js/modules/dom_module.js
new file mode 100644
index 000000000..d3d94dbff
--- /dev/null
+++ b/resources/static/common/js/modules/dom_module.js
@@ -0,0 +1,83 @@
+/*jshint browser:true, jquery: true, forin: true, laxbreak:true */
+/*global BrowserID: true*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+BrowserID.Modules.DOMModule = (function() {
+"use strict";
+
+  var bid = BrowserID,
+      dom = bid.DOM,
+      helpers = bid.Helpers,
+      cancelEvent = helpers.cancelEvent,
+      mediator = bid.Mediator,
+      sc;
+
+  /*
+   * DOMModule provides modules DOM related functionality.
+   */
+
+  var Module = bid.Modules.Module.extend({
+    init: function(options) {
+      sc.init.call(this, options);
+
+      this.domEvents = [];
+    },
+
+    stop: function() {
+      this.unbindAll();
+      sc.stop.call(this);
+    },
+
+    /**
+     * Bind a dom event
+     * @method bind
+     * @param {string} target - css selector
+     * @param {string} type - event type
+     * @param {function} callback
+     * @param {object} [context] - optional context, if not given, use this.
+     */
+    bind: function(target, type, callback, context) {
+      var self=this,
+          cb = callback.bind(context || this);
+
+      dom.bindEvent(target, type, cb);
+
+      self.domEvents.push({
+        target: target,
+        type: type,
+        cb: cb
+      });
+    },
+
+    /**
+     * Shortcut to bind a click handler
+     * @method click
+     * @param {string}
+     * @param {function} callback
+     * @param {object} [context] - optional context, if not given, use this.
+     */
+    click: function(target, callback, context) {
+      this.bind(target, "click", cancelEvent(callback), context);
+    },
+
+    /**
+     * Unbind all DOM event handlers
+     * @method unbindAll
+     */
+    unbindAll: function() {
+      var self=this,
+          evt;
+
+      while(evt = self.domEvents.pop()) {
+        dom.unbindEvent(evt.target, evt.type, evt.cb);
+      }
+   }
+
+  });
+
+  sc = Module.sc;
+
+  return Module;
+
+}());
diff --git a/resources/static/common/js/modules/module.js b/resources/static/common/js/modules/module.js
new file mode 100644
index 000000000..7f67e17b4
--- /dev/null
+++ b/resources/static/common/js/modules/module.js
@@ -0,0 +1,80 @@
+/*jshint browser:true, jquery: true, forin: true, laxbreak:true */
+/*global BrowserID: true, _:true */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+BrowserID.Modules = BrowserID.Modules || {};
+BrowserID.Modules.Module = (function() {
+"use strict";
+
+  var bid = BrowserID,
+      mediator = bid.Mediator;
+
+  /*
+   * Module is the root of all modules. Provides basic pub/sub mechanisms,
+   * ability to start and stop, check for required arguments, etc.
+   */
+
+  var Module = BrowserID.Class({
+    init: function(options) {
+      this.subscriptions = [];
+    },
+
+    checkRequired: function(options) {
+      var list = [].slice.call(arguments, 1);
+      for(var item, index = 0; item = list[index]; ++index) {
+        if(!options.hasOwnProperty(item)) {
+          throw "missing config option: " + item;
+        }
+      }
+    },
+
+    start: function(options) {
+      var self=this;
+      self.options = options || {};
+    },
+
+    stop: function() {
+      _.each(this.subscriptions, mediator.unsubscribe);
+      this.subscriptions = [];
+    },
+
+    destroy: function() {
+      this.stop();
+    },
+
+    /**
+     * Publish a message to the mediator.
+     * @method publish
+     * @param {string} message
+     * @param {object} data
+     */
+    publish: mediator.publish.bind(mediator),
+
+    /**
+     * Subscribe to a message on the mediator.
+     * @method subscribe
+     * @param {string} message
+     * @param {function} callback
+     * @param {object} [context] - context, if not given, use this.
+     */
+    subscribe: function(message, callback, context) {
+      var id = mediator.subscribe(message, callback, context || this);
+      this.subscriptions.push(id);
+    },
+
+    /**
+     * Subscribe to all messages on the mediator.
+     * @method subscribeAll
+     * @param {function} callback
+     * @param {object} [context] - context, if not given, use this.
+     */
+    subscribeAll: function(callback, context) {
+      var id = mediator.subscribeAll(callback, context || this);
+      this.subscriptions.push(id);
+    }
+  });
+
+  return Module;
+
+}());
diff --git a/resources/static/common/js/modules/page_module.js b/resources/static/common/js/modules/page_module.js
index abf39c126..52deb2332 100644
--- a/resources/static/common/js/modules/page_module.js
+++ b/resources/static/common/js/modules/page_module.js
@@ -3,17 +3,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-BrowserID.Modules = BrowserID.Modules || {};
 BrowserID.Modules.PageModule = (function() {
 "use strict";
 
-  var ANIMATION_TIME = 250,
-      bid = BrowserID,
+  /*
+   * PageModule provides functionality for screens on
+   * either the main site or in the dialog.
+   */
+
+  var bid = BrowserID,
       dom = bid.DOM,
       screens = bid.Screens,
       helpers = bid.Helpers,
       cancelEvent = helpers.cancelEvent,
-      mediator = bid.Mediator;
+      sc;
 
    function onSubmit() {
      if (!dom.hasClass("body", "submit_disabled") && this.validate()) {
@@ -36,80 +39,18 @@ BrowserID.Modules.PageModule = (function() {
     screen.hide();
   }
 
-  var Module = BrowserID.Class({
-    init: function(options) {
-      options = options || {};
-
-      var self=this;
-
-      self.domEvents = [];
-    },
-
-    checkRequired: function(options) {
-      var list = [].slice.call(arguments, 1);
-      for(var item, index = 0; item = list[index]; ++index) {
-        if(!options.hasOwnProperty(item)) {
-          throw "missing config option: " + item;
-        }
-      }
-    },
-
+  var Module = bid.Modules.DOMModule.extend({
     start: function(options) {
       var self=this;
-      self.options = options || {};
+
+      sc.start.call(self, options);
 
       self.bind("form", "submit", cancelEvent(onSubmit));
     },
 
     stop: function() {
-      this.unbindAll();
-
       dom.removeClass("body", "waiting");
-    },
-
-    destroy: function() {
-      this.stop();
-    },
-
-    /**
-     * Bind a dom event
-     * @method bind
-     * @param {string} target - css selector
-     * @param {string} type - event type
-     * @param {function} callback
-     * @param {object} [context] - optional context, if not given, use this.
-     */
-    bind: function(target, type, callback, context) {
-      var self=this,
-          cb = callback.bind(context || this);
-
-      dom.bindEvent(target, type, cb);
-
-      self.domEvents.push({
-        target: target,
-        type: type,
-        cb: cb
-      });
-    },
-
-    /**
-     * Shortcut to bind a click handler
-     * @method click
-     * @param {string}
-     * @param {function} callback
-     * @param {object} [context] - optional context, if not given, use this.
-     */
-    click: function(target, callback, context) {
-      this.bind(target, "click", cancelEvent(callback), context);
-    },
-
-    unbindAll: function() {
-      var self=this,
-          evt;
-
-      while(evt = self.domEvents.pop()) {
-        dom.unbindEvent(evt.target, evt.type, evt.cb);
-      }
+      sc.stop.call(this);
     },
 
     renderDialog: function(template, data) {
@@ -155,7 +96,7 @@ BrowserID.Modules.PageModule = (function() {
     submit: function() {
     },
 
-    // XXX maybe we should not get rid of this.
+    // XXX maybe we should get rid of this.
     close: function(message) {
       this.destroy();
       if (message) {
@@ -163,35 +104,6 @@ BrowserID.Modules.PageModule = (function() {
       }
     },
 
-    /**
-     * Publish a message to the mediator.
-     * @method publish
-     * @param {string} message
-     * @param {object} data
-     */
-    publish: mediator.publish.bind(mediator),
-
-    /**
-     * Subscribe to a message on the mediator.
-     * @method subscribe
-     * @param {string} message
-     * @param {function} callback
-     * @param {object} [context] - context, if not given, use this.
-     */
-    subscribe: function(message, callback, context) {
-      mediator.subscribe(message, callback, context || this);
-    },
-
-    /**
-     * Subscribe to all messages on the mediator.
-     * @method subscribeAll
-     * @param {function} callback
-     * @param {object} [context] - context, if not given, use this.
-     */
-    subscribeAll: function(callback, context) {
-      mediator.subscribeAll(callback, context || this);
-    },
-
     /**
      * Get a curried function to an error dialog.
      * @method getErrorDialog
@@ -215,6 +127,8 @@ BrowserID.Modules.PageModule = (function() {
     // END TESTING API
   });
 
+  sc = Module.sc;
+
   return Module;
 
 }());
diff --git a/resources/static/test/cases/common/js/modules/dom_module.js b/resources/static/test/cases/common/js/modules/dom_module.js
new file mode 100644
index 000000000..9adc40a53
--- /dev/null
+++ b/resources/static/test/cases/common/js/modules/dom_module.js
@@ -0,0 +1,75 @@
+/*jshint browser: true, forin: true, laxbreak: true */
+/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+(function() {
+  "use strict";
+
+  var controller, el,
+      bid = BrowserID,
+      mediator = bid.Mediator;
+
+  function createController(options) {
+    controller = bid.Modules.DOMModule.create(options);
+    controller.start();
+  }
+
+  module("common/js/modules/dom_module", {
+    setup: function() {
+      bid.TestHelpers.setup();
+    },
+
+    teardown: function() {
+      controller.destroy();
+      bid.TestHelpers.teardown();
+    }
+  });
+
+  asyncTest("bind DOM Events", function() {
+    createController();
+
+   controller.bind("body", "click", function(event) {
+      event.preventDefault();
+
+      strictEqual(this, controller, "context is correct");
+      start();
+   });
+
+   $("body").trigger("click");
+  });
+
+  asyncTest("click - bind a click handler, handler does not get event", function() {
+    createController();
+
+    controller.click("body", function(event) {
+      equal(typeof event, "undefined", "event is undefined");
+      strictEqual(this, controller, "context is correct");
+      start();
+    });
+
+    $("body").trigger("click");
+  });
+
+  asyncTest("unbindAll removes all listeners", function() {
+    createController();
+    var listenerCalled = false;
+
+    controller.bind("body", "click", function(event) {
+      event.preventDefault();
+
+      listenerCalled = true;
+    });
+
+    controller.unbindAll();
+
+    $("body").trigger("click");
+
+    setTimeout(function() {
+      equal(listenerCalled, false, "all events are unbound, listener should not be called");
+      start();
+    }, 1);
+  });
+
+}());
+
diff --git a/resources/static/test/cases/common/js/modules/module.js b/resources/static/test/cases/common/js/modules/module.js
new file mode 100644
index 000000000..e26e67a8b
--- /dev/null
+++ b/resources/static/test/cases/common/js/modules/module.js
@@ -0,0 +1,95 @@
+/*jshint browser: true, forin: true, laxbreak: true */
+/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, strictEqual: true, BrowserID:true */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+(function() {
+  "use strict";
+
+  var controller,
+      bid = BrowserID,
+      mediator = bid.Mediator;
+
+  function createController(options) {
+    controller = bid.Modules.Module.create(options);
+    controller.start();
+  }
+
+  module("common/js/modules/module", {
+    setup: function() {
+      bid.TestHelpers.setup();
+    },
+
+    teardown: function() {
+      controller.destroy();
+      bid.TestHelpers.teardown();
+    }
+  });
+
+  test("subscribe/stop - listens to messages from the mediator. All bindings are removed whenever stop is called", function() {
+    createController();
+    var count = 0;
+    controller.subscribe("message", function(msg, data) {
+      strictEqual(this, controller, "context set to the controller");
+      equal(msg, "message", "correct message passed");
+      equal(data.field, 1, "correct data passed");
+      count++;
+    });
+
+    mediator.publish("message", { field: 1 });
+    equal(1, count, "subscriber called");
+
+    // Subscriptions should be removed on stop
+    controller.stop();
+    mediator.publish("after_stop");
+    equal(1, count, "subscriptions are removed on stop");
+  });
+
+  test("subscribeAll - listen for ALL messages", function() {
+    createController();
+    var count = 0;
+    controller.subscribeAll(function(msg, data) {
+      count++;
+    });
+
+    var messages = ["message1", "message2", "message3"];
+    _.each(messages, mediator.publish);
+
+    equal(count, messages.length, "subscriber called for all messages");
+
+    // Subscriptions should be removed on stop
+    controller.stop();
+    mediator.publish("after_stop");
+    equal(count, messages.length, "subscriptions are removed on stop");
+  });
+
+  asyncTest("publish - publish messages to the mediator", function() {
+    createController();
+
+    mediator.subscribe("message", function(msg, data) {
+      equal(msg, "message", "message is correct");
+      equal(data.field, 1, "data passed correctly");
+      start();
+    });
+
+    controller.publish("message", {
+      field: 1
+    });
+  });
+
+  test("checkRequired", function() {
+    createController();
+
+    var error;
+    try {
+      controller.checkRequired({}, "requiredField");
+    }
+    catch(e) {
+      error = e;
+    }
+
+    equal(error, "missing config option: requiredField");
+  });
+
+}());
+
diff --git a/resources/static/test/cases/common/js/modules/page_module.js b/resources/static/test/cases/common/js/modules/page_module.js
index 37f5f8bbc..26722c2da 100644
--- a/resources/static/test/cases/common/js/modules/page_module.js
+++ b/resources/static/test/cases/common/js/modules/page_module.js
@@ -96,91 +96,6 @@
     func();
   });
 
-  asyncTest("bind DOM Events", function() {
-    createController();
-
-   controller.bind("body", "click", function(event) {
-      event.preventDefault();
-
-      strictEqual(this, controller, "context is correct");
-      start();
-   });
-
-   $("body").trigger("click");
-  });
-
-  asyncTest("click - bind a click handler, handler does not get event", function() {
-    createController();
-
-    controller.click("body", function(event) {
-      equal(typeof event, "undefined", "event is undefined");
-      strictEqual(this, controller, "context is correct");
-      start();
-    });
-
-    $("body").trigger("click");
-  });
-
-  asyncTest("unbindAll removes all listeners", function() {
-    createController();
-    var listenerCalled = false;
-
-    controller.bind("body", "click", function(event) {
-      event.preventDefault();
-
-      listenerCalled = true;
-    });
-
-    controller.unbindAll();
-
-    $("body").trigger("click");
-
-    setTimeout(function() {
-      equal(listenerCalled, false, "all events are unbound, listener should not be called");
-      start();
-    }, 1);
-  });
-
-  asyncTest("subscribe - listens to messages from the mediator", function() {
-    createController();
-    controller.subscribe("message", function(msg, data) {
-      strictEqual(this, controller, "context set to the controller");
-      equal(msg, "message", "correct message passed");
-      equal(data.field, 1, "correct data passed");
-      start();
-    });
-
-    mediator.publish("message", { field: 1 });
-  });
-
-  asyncTest("publish - publish messages to the mediator", function() {
-    createController();
-
-    mediator.subscribe("message", function(msg, data) {
-      equal(msg, "message", "message is correct");
-      equal(data.field, 1, "data passed correctly");
-      start();
-    });
-
-    controller.publish("message", {
-      field: 1
-    });
-  });
-
-  test("checkRequired", function() {
-    createController();
-
-    var error;
-    try {
-      controller.checkRequired({}, "requiredField");
-    }
-    catch(e) {
-      error = e;
-    }
-
-    equal(error, "missing config option: requiredField");
-  });
-
   test("form is not submitted when 'submit_disabled' class is added to body", function() {
     createController();
 
diff --git a/resources/views/test.ejs b/resources/views/test.ejs
index 8daa136d4..01012a801 100644
--- a/resources/views/test.ejs
+++ b/resources/views/test.ejs
@@ -109,10 +109,11 @@
     <script src="/common/js/command.js"></script>
     <script src="/common/js/history.js"></script>
     <script src="/common/js/state_machine.js"></script>
-
     <script src="/common/js/models/models.js"></script>
     <script src="/common/js/models/interaction_data.js"></script>
 
+    <script src="/common/js/modules/module.js"></script>
+    <script src="/common/js/modules/dom_module.js"></script>
     <script src="/common/js/modules/page_module.js"></script>
     <script src="/common/js/modules/xhr_delay.js"></script>
     <script src="/common/js/modules/xhr_disable_form.js"></script>
@@ -168,6 +169,8 @@
 
     <script src="cases/common/js/models/interaction_data.js"></script>
 
+    <script src="cases/common/js/modules/module.js"></script>
+    <script src="cases/common/js/modules/dom_module.js"></script>
     <script src="cases/common/js/modules/page_module.js"></script>
     <script src="cases/common/js/modules/xhr_delay.js"></script>
     <script src="cases/common/js/modules/xhr_disable_form.js"></script>
-- 
GitLab