From 244583b2f790f21519745452fe7d87147c2d84f1 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 15 Jan 2015 13:36:21 -0800 Subject: [PATCH 01/11] [Forms] Separate out FormController Separate out the controller for mct-form from its directive definition, to allow reuse by the mct-toolbar directive, WTD-684. --- platform/forms/src/MCTForm.js | 51 ++------------ .../forms/src/controllers/FormController.js | 57 +++++++++++++++ .../test/controllers/FormControllerSpec.js | 69 +++++++++++++++++++ platform/forms/test/suite.json | 3 +- 4 files changed, 132 insertions(+), 48 deletions(-) create mode 100644 platform/forms/src/controllers/FormController.js create mode 100644 platform/forms/test/controllers/FormControllerSpec.js diff --git a/platform/forms/src/MCTForm.js b/platform/forms/src/MCTForm.js index 8414938171..0de41797f7 100644 --- a/platform/forms/src/MCTForm.js +++ b/platform/forms/src/MCTForm.js @@ -4,13 +4,10 @@ * Module defining MCTForm. Created by vwoeltje on 11/10/14. */ define( - [], - function () { + ["./controllers/FormController"], + function (FormController) { "use strict"; - // Default ng-pattern; any non whitespace - var NON_WHITESPACE = /\S/; - /** * The mct-form directive allows generation of displayable * forms based on a declarative description of the form's @@ -37,45 +34,6 @@ define( "templates/form.html" ].join("/"); - function controller($scope) { - var regexps = []; - - // ng-pattern seems to want a RegExp, and not a - // string (despite what documentation says) but - // we want form structure to be JSON-expressible, - // so we make RegExp's from strings as-needed - function getRegExp(pattern) { - // If undefined, don't apply a pattern - if (!pattern) { - return NON_WHITESPACE; - } - - // Just echo if it's already a regexp - if (pattern instanceof RegExp) { - return pattern; - } - - // Otherwise, assume a string - // Cache for easy lookup later (so we don't - // creat a new RegExp every digest cycle) - if (!regexps[pattern]) { - regexps[pattern] = new RegExp(pattern); - } - - return regexps[pattern]; - } - - // Publish the form state under the requested - // name in the parent scope - $scope.$watch("mctForm", function (mctForm) { - if ($scope.name) { - $scope.$parent[$scope.name] = mctForm; - } - }); - - $scope.getRegExp = getRegExp; - } - return { // Only show at the element level restrict: "E", @@ -83,9 +41,8 @@ define( // Load the forms template templateUrl: templatePath, - // Use the controller defined above to - // populate/respond to changes in scope - controller: controller, + // Use FormController to populate/respond to changes in scope + controller: FormController, // Initial an isolate scope scope: { diff --git a/platform/forms/src/controllers/FormController.js b/platform/forms/src/controllers/FormController.js new file mode 100644 index 0000000000..ff7a1b0769 --- /dev/null +++ b/platform/forms/src/controllers/FormController.js @@ -0,0 +1,57 @@ +/*global define*/ + +define( + [], + function () { + "use strict"; + + // Default ng-pattern; any non whitespace + var NON_WHITESPACE = /\S/; + + /** + * Controller for mct-form and mct-toolbar directives. + * @constructor + */ + function FormController($scope) { + var regexps = []; + + // ng-pattern seems to want a RegExp, and not a + // string (despite what documentation says) but + // we want form structure to be JSON-expressible, + // so we make RegExp's from strings as-needed + function getRegExp(pattern) { + // If undefined, don't apply a pattern + if (!pattern) { + return NON_WHITESPACE; + } + + // Just echo if it's already a regexp + if (pattern instanceof RegExp) { + return pattern; + } + + // Otherwise, assume a string + // Cache for easy lookup later (so we don't + // creat a new RegExp every digest cycle) + if (!regexps[pattern]) { + regexps[pattern] = new RegExp(pattern); + } + + return regexps[pattern]; + } + + // Publish the form state under the requested + // name in the parent scope + $scope.$watch("mctForm", function (mctForm) { + if ($scope.name) { + $scope.$parent[$scope.name] = mctForm; + } + }); + + // Expose the regexp lookup + $scope.getRegExp = getRegExp; + } + + return FormController; + } +); \ No newline at end of file diff --git a/platform/forms/test/controllers/FormControllerSpec.js b/platform/forms/test/controllers/FormControllerSpec.js new file mode 100644 index 0000000000..538e4e5156 --- /dev/null +++ b/platform/forms/test/controllers/FormControllerSpec.js @@ -0,0 +1,69 @@ +/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../../src/controllers/FormController"], + function (FormController) { + "use strict"; + + describe("The form controller", function () { + var mockScope, + controller; + + beforeEach(function () { + mockScope = jasmine.createSpyObj("$scope", [ "$watch" ]); + mockScope.$parent = {}; + controller = new FormController(mockScope); + }); + + it("watches for changes in form by name", function () { + expect(mockScope.$watch).toHaveBeenCalledWith( + "mctForm", + jasmine.any(Function) + ); + }); + + it("conveys form status to parent scope", function () { + var someState = { someKey: "some value" }; + mockScope.name = "someName"; + mockScope.$watch.mostRecentCall.args[1](someState); + expect(mockScope.$parent.someName).toBe(someState); + }); + + it("allows strings to be converted to RegExps", function () { + // Should have added getRegExp to the scope, + // to convert strings to regular expressions + expect(mockScope.getRegExp("^\\d+$")).toEqual(/^\d+$/); + }); + + it("returns the same regexp instance for the same string", function () { + // Don't want new instances each digest cycle, for performance + var strRegExp = "^[a-z]\\d+$", + regExp; + + // Add getRegExp to scope + regExp = mockScope.getRegExp(strRegExp); + + // Same object instance each time... + expect(mockScope.getRegExp(strRegExp)).toBe(regExp); + expect(mockScope.getRegExp(strRegExp)).toBe(regExp); + }); + + it("passes RegExp objects through untouched", function () { + // Permit using forms to simply provide their own RegExp object + var regExp = /^\d+[a-d]$/; + + // Should have added getRegExp to the scope, + // to convert strings to regular expressions + expect(mockScope.getRegExp(regExp)).toBe(regExp); + }); + + it("passes a non-whitespace regexp when no pattern is defined", function () { + // If no pattern is supplied, ng-pattern should match anything + expect(mockScope.getRegExp()).toEqual(/\S/); + expect(mockScope.getRegExp(undefined)).toEqual(/\S/); + }); + + + }); + } +); \ No newline at end of file diff --git a/platform/forms/test/suite.json b/platform/forms/test/suite.json index f69c3a325e..61eff300e6 100644 --- a/platform/forms/test/suite.json +++ b/platform/forms/test/suite.json @@ -2,5 +2,6 @@ "MCTControl", "MCTForm", "controllers/CompositeController", - "controllers/DateTimeController" + "controllers/DateTimeController", + "controllers/FormController" ] \ No newline at end of file From aed6787f2c50d015a6063c1ba5f396056ba148df Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 15 Jan 2015 14:23:01 -0800 Subject: [PATCH 02/11] [Forms] Copy mct-form to mct-toolbar Copy mct-form template and scripts to use as a basis for mct-toolbar. WTD-684. --- platform/forms/bundle.json | 4 + platform/forms/res/templates/toolbar.html | 42 +++++++++++ platform/forms/src/MCTToolbar.js | 64 ++++++++++++++++ platform/forms/test/MCTToolbarSpec.js | 90 +++++++++++++++++++++++ 4 files changed, 200 insertions(+) create mode 100644 platform/forms/res/templates/toolbar.html create mode 100644 platform/forms/src/MCTToolbar.js create mode 100644 platform/forms/test/MCTToolbarSpec.js diff --git a/platform/forms/bundle.json b/platform/forms/bundle.json index 5654d80f2b..020438fb66 100644 --- a/platform/forms/bundle.json +++ b/platform/forms/bundle.json @@ -7,6 +7,10 @@ "key": "mctForm", "implementation": "MCTForm.js" }, + { + "key": "mctToolbar", + "implementation": "MCTToolbar.js" + }, { "key": "mctControl", "implementation": "MCTControl.js", diff --git a/platform/forms/res/templates/toolbar.html b/platform/forms/res/templates/toolbar.html new file mode 100644 index 0000000000..e2d6868263 --- /dev/null +++ b/platform/forms/res/templates/toolbar.html @@ -0,0 +1,42 @@ +
+ +
+ +
+ {{section.name}} +
+
+ +
+ +
+ {{row.name}} + + i + +
+
+
+ + +
+
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/platform/forms/src/MCTToolbar.js b/platform/forms/src/MCTToolbar.js new file mode 100644 index 0000000000..c4642e4544 --- /dev/null +++ b/platform/forms/src/MCTToolbar.js @@ -0,0 +1,64 @@ +/*global define,Promise*/ + +/** + * Module defining MCTForm. Created by vwoeltje on 11/10/14. + */ +define( + ["./controllers/FormController"], + function (FormController) { + "use strict"; + + /** + * The mct-toolbar directive allows generation of displayable + * forms based on a declarative description of the form's + * structure. + * + * This directive accepts three attributes: + * + * * `ng-model`: The model for the form; where user input + * where be stored. + * * `structure`: The declarative structure of the toolbar. + * Describes what controls should be shown and where + * their values should be read/written in the model. + * * `name`: The name under which to expose the form's + * dirty/valid state. This is similar to ng-form's use + * of name, except this will be made available in the + * parent scope. + * + * @constructor + */ + function MCTForm() { + var templatePath = [ + "platform/forms", //MCTForm.bundle.path, + "res", //MCTForm.bundle.resources, + "templates/toolbar.html" + ].join("/"); + + return { + // Only show at the element level + restrict: "E", + + // Load the forms template + templateUrl: templatePath, + + // Use FormController to populate/respond to changes in scope + controller: FormController, + + // Initial an isolate scope + scope: { + + // The model: Where form input will actually go + ngModel: "=", + + // Form structure; what sections/rows to show + structure: "=", + + // Name under which to publish the form + name: "@" + } + }; + } + + return MCTForm; + } +); \ No newline at end of file diff --git a/platform/forms/test/MCTToolbarSpec.js b/platform/forms/test/MCTToolbarSpec.js new file mode 100644 index 0000000000..d18ff40ed6 --- /dev/null +++ b/platform/forms/test/MCTToolbarSpec.js @@ -0,0 +1,90 @@ +/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../src/MCTToolbar"], + function (MCTToolbar) { + "use strict"; + + describe("The mct-toolbar directive", function () { + var mockScope, + mctToolbar; + + beforeEach(function () { + mockScope = jasmine.createSpyObj("$scope", [ "$watch" ]); + mockScope.$parent = {}; + mctToolbar = new MCTToolbar(); + }); + + it("is restricted to elements", function () { + expect(mctToolbar.restrict).toEqual("E"); + }); + + it("watches for changes in form by name", function () { + // mct-form needs to watch for the form by name + // in order to convey changes in $valid, $dirty, etc + // up to the parent scope. + mctToolbar.controller(mockScope); + + expect(mockScope.$watch).toHaveBeenCalledWith( + "mctForm", + jasmine.any(Function) + ); + }); + + it("conveys form status to parent scope", function () { + var someState = { someKey: "some value" }; + mockScope.name = "someName"; + + mctToolbar.controller(mockScope); + + mockScope.$watch.mostRecentCall.args[1](someState); + + expect(mockScope.$parent.someName).toBe(someState); + }); + + it("allows strings to be converted to RegExps", function () { + // This is needed to support ng-pattern in the template + mctToolbar.controller(mockScope); + + // Should have added getRegExp to the scope, + // to convert strings to regular expressions + expect(mockScope.getRegExp("^\\d+$")).toEqual(/^\d+$/); + }); + + it("returns the same regexp instance for the same string", function () { + // Don't want new instances each digest cycle, for performance + var strRegExp = "^[a-z]\\d+$", + regExp; + + // Add getRegExp to scope + mctToolbar.controller(mockScope); + regExp = mockScope.getRegExp(strRegExp); + + // Same object instance each time... + expect(mockScope.getRegExp(strRegExp)).toBe(regExp); + expect(mockScope.getRegExp(strRegExp)).toBe(regExp); + }); + + it("passes RegExp objects through untouched", function () { + // Permit using forms to simply provide their own RegExp object + var regExp = /^\d+[a-d]$/; + + // Add getRegExp to scope + mctToolbar.controller(mockScope); + + // Should have added getRegExp to the scope, + // to convert strings to regular expressions + expect(mockScope.getRegExp(regExp)).toBe(regExp); + }); + + it("passes a non-whitespace regexp when no pattern is defined", function () { + // If no pattern is supplied, ng-pattern should match anything + mctToolbar.controller(mockScope); + expect(mockScope.getRegExp()).toEqual(/\S/); + expect(mockScope.getRegExp(undefined)).toEqual(/\S/); + }); + + + }); + } +); \ No newline at end of file From 72cd21171dbce8d1b3ce2583d1ba72a5c52f4a71 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 15 Jan 2015 14:24:12 -0800 Subject: [PATCH 03/11] [Forms] Remove obsolete templates Remove obsolete template declarations from the bundle definition for platform/forms. WTD-684. --- platform/forms/bundle.json | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/platform/forms/bundle.json b/platform/forms/bundle.json index 020438fb66..9b73d846a6 100644 --- a/platform/forms/bundle.json +++ b/platform/forms/bundle.json @@ -49,36 +49,6 @@ "key": "CompositeController", "implementation": "controllers/CompositeController.js" } - ], - "templates": [ - { - "key": "_checkbox", - "templateUrl": "templates/_checkbox.html" - }, - { - "key": "_checkboxes", - "templateUrl": "templates/_checkboxes.html" - }, - { - "key": "_datetime", - "templateUrl": "templates/_datetime.html" - }, - { - "key": "_select", - "templateUrl": "templates/_select.html" - }, - { - "key": "_selects", - "templateUrl": "templates/_selects.html" - }, - { - "key": "_textfield", - "templateUrl": "templates/_textfield.html" - }, - { - "key": "_textfields", - "templateUrl": "templates/_textfields.html" - } ] } } \ No newline at end of file From fa9df65ddf2095fd5eeb514f68d04eb370dea856 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 15 Jan 2015 15:06:40 -0800 Subject: [PATCH 04/11] [Forms] Transition toolbar markup Transition markup from pre-Angular to complete toolbar template, WTD-684. --- platform/forms/res/templates/toolbar.html | 59 +++++++++-------------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/platform/forms/res/templates/toolbar.html b/platform/forms/res/templates/toolbar.html index e2d6868263..2c7558eac2 100644 --- a/platform/forms/res/templates/toolbar.html +++ b/platform/forms/res/templates/toolbar.html @@ -1,41 +1,30 @@ -
+
- -
- {{section.name}} -
-
- -
+ + + + + + + + + -
- {{row.name}} - - i - -
-
-
- - -
-
-
-
-
From 4b51c370674bfb85a4c432fb9a563e22bc46f5c3 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 15 Jan 2015 15:10:57 -0800 Subject: [PATCH 05/11] [Forms] Allow textfield size to be specified Allow specification of size per-textfield from toolbar structure definitions; WTD-684. --- platform/forms/res/templates/controls/textfield.html | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/forms/res/templates/controls/textfield.html b/platform/forms/res/templates/controls/textfield.html index 57e61d3613..148f9e7005 100644 --- a/platform/forms/res/templates/controls/textfield.html +++ b/platform/forms/res/templates/controls/textfield.html @@ -4,6 +4,7 @@ ng-required="ngRequired" ng-model="ngModel[field]" ng-pattern="ngPattern" + size="{{structure.size}}" name="mctControl"> From b5393ae9da7860426b8f398b9389456d5d13fae6 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 15 Jan 2015 15:26:27 -0800 Subject: [PATCH 06/11] [Forms] Add button control Add an mct-control for buttons, for use in toolbars. WTD-684. --- platform/forms/bundle.json | 4 ++++ platform/forms/res/templates/controls/button.html | 11 +++++++++++ platform/forms/res/templates/toolbar.html | 4 ++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 platform/forms/res/templates/controls/button.html diff --git a/platform/forms/bundle.json b/platform/forms/bundle.json index 9b73d846a6..2e797fb7a0 100644 --- a/platform/forms/bundle.json +++ b/platform/forms/bundle.json @@ -34,6 +34,10 @@ "key": "textfield", "templateUrl": "templates/controls/textfield.html" }, + { + "key": "button", + "templateUrl": "templates/controls/button.html" + }, { "key": "composite", "templateUrl": "templates/controls/composite.html" diff --git a/platform/forms/res/templates/controls/button.html b/platform/forms/res/templates/controls/button.html new file mode 100644 index 0000000000..730a67dd8b --- /dev/null +++ b/platform/forms/res/templates/controls/button.html @@ -0,0 +1,11 @@ + + + {{structure.glyph}} + + + {{structure.text}} + + diff --git a/platform/forms/res/templates/toolbar.html b/platform/forms/res/templates/toolbar.html index 2c7558eac2..49ac754421 100644 --- a/platform/forms/res/templates/toolbar.html +++ b/platform/forms/res/templates/toolbar.html @@ -3,12 +3,12 @@
+ title="{{section.description}}">