mirror of
https://github.com/nasa/openmct.git
synced 2024-12-25 07:41:06 +00:00
[Forms] Merge in forms bundle
Merge in forms bundle from its development branch to begin work integrating Forms into the general UI, for WTD-593.
This commit is contained in:
commit
2bee842509
@ -10,6 +10,7 @@
|
||||
"platform/features/layout",
|
||||
"platform/features/plot",
|
||||
"platform/features/scrolling",
|
||||
"platform/forms",
|
||||
|
||||
"example/generator",
|
||||
"example/persistence"
|
||||
|
18
example/forms/bundle.json
Normal file
18
example/forms/bundle.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Declarative Forms example",
|
||||
"sources": "src",
|
||||
"extensions": {
|
||||
"controllers": [
|
||||
{
|
||||
"key": "ExampleFormController",
|
||||
"implementation": "ExampleFormController.js",
|
||||
"depends": [ "$scope" ]
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"templateUrl": "templates/exampleForm.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
19
example/forms/res/templates/exampleForm.html
Normal file
19
example/forms/res/templates/exampleForm.html
Normal file
@ -0,0 +1,19 @@
|
||||
<div ng-controller="ExampleFormController">
|
||||
<mct-form structure="form" ng-model="state" name="aForm">
|
||||
</mct-form>
|
||||
|
||||
|
||||
<ul>
|
||||
<li>Dirty: {{aForm.$dirty}}</li>
|
||||
<li>Valid: {{aForm.$valid}}</li>
|
||||
</ul>
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
<textarea>
|
||||
{{state | json}}
|
||||
</textarea>
|
||||
|
||||
</pre>
|
||||
</div>
|
79
example/forms/src/ExampleFormController.js
Normal file
79
example/forms/src/ExampleFormController.js
Normal file
@ -0,0 +1,79 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
function ExampleFormController($scope) {
|
||||
$scope.state = {
|
||||
|
||||
};
|
||||
|
||||
$scope.form = {
|
||||
name: "An example form.",
|
||||
sections: [
|
||||
{
|
||||
name: "First section",
|
||||
rows: [
|
||||
{
|
||||
name: "Check me",
|
||||
control: "checkbox",
|
||||
key: "checkMe"
|
||||
},
|
||||
{
|
||||
name: "Enter your name",
|
||||
required: true,
|
||||
control: "textfield",
|
||||
key: "yourName"
|
||||
},
|
||||
{
|
||||
name: "Enter a number",
|
||||
control: "textfield",
|
||||
pattern: "^\\d+$",
|
||||
key: "aNumber"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Second section",
|
||||
rows: [
|
||||
{
|
||||
name: "Pick a date",
|
||||
required: true,
|
||||
description: "Enter date in form YYYY-DDD",
|
||||
control: "datetime",
|
||||
key: "aDate"
|
||||
},
|
||||
{
|
||||
name: "Choose something",
|
||||
control: "select",
|
||||
options: [
|
||||
{ name: "Hats", value: "hats" },
|
||||
{ name: "Bats", value: "bats" },
|
||||
{ name: "Cats", value: "cats" },
|
||||
{ name: "Mats", value: "mats" }
|
||||
],
|
||||
key: "aChoice"
|
||||
},
|
||||
{
|
||||
name: "Choose something",
|
||||
control: "select",
|
||||
required: true,
|
||||
options: [
|
||||
{ name: "Hats", value: "hats" },
|
||||
{ name: "Bats", value: "bats" },
|
||||
{ name: "Cats", value: "cats" },
|
||||
{ name: "Mats", value: "mats" }
|
||||
],
|
||||
key: "aRequiredChoice"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
return ExampleFormController;
|
||||
}
|
||||
);
|
@ -1,3 +1,87 @@
|
||||
# Overview
|
||||
|
||||
This bundle contains a general implementation of forms in Open MCT Web.
|
||||
This allows forms to be expressed using a reasonably concise declarative
|
||||
syntax, and rendered as Angular templates in a consistent fashion.
|
||||
|
||||
# Usage
|
||||
|
||||
To include a form with a declarative definition, use the `mct-form`
|
||||
directive, e.g.:
|
||||
|
||||
<mct-form ng-model="myModel" structure="myStructure" name="myForm">
|
||||
</mct-form>
|
||||
|
||||
The attributes utilized by this form are as follows:
|
||||
|
||||
* `ng-model`: The object which should contain the full form input. Individual
|
||||
fields in this model are bound to individual controls; the names used for
|
||||
these fields are provided in the form structure (see below).
|
||||
* `structure`: The structure of the form; e.g. sections, rows, their names,
|
||||
and so forth. The value of this attribute should be an Angular expression.
|
||||
* `name`: The name in the containing scope under which to publish form
|
||||
"meta-state", e.g. `$valid`, `$dirty`, etc. This is as the behavior of
|
||||
`ng-form`. Passed as plain text in the attribute.
|
||||
|
||||
## Form structure
|
||||
|
||||
A form's structure is described as a JavaScript object in the following form:
|
||||
|
||||
{
|
||||
"name": ... title to display for the form, as a string ...,
|
||||
"sections": [
|
||||
{
|
||||
"name": ... title to display for the section ...,
|
||||
"rows": [
|
||||
{
|
||||
"name": ... title to display for this row ...,
|
||||
"control": ... symbolic key for the control ...,
|
||||
"key": ... field name in ng-model ...
|
||||
"pattern": ... optional, reg exp to match against ...
|
||||
"required": ... optional boolean ...
|
||||
"options": [
|
||||
"name": ... name to display (e.g. in a select) ...,
|
||||
"value": ... value to store in the model ...
|
||||
]
|
||||
},
|
||||
... and other rows ...
|
||||
]
|
||||
},
|
||||
... and other sections ...
|
||||
]
|
||||
}
|
||||
|
||||
Note that `pattern` may be specified as a string, to simplify storing
|
||||
for structures as JSON when necessary. The string should be given in
|
||||
a form appropriate to pass to a `RegExp` constructor.
|
||||
|
||||
## Adding controls
|
||||
|
||||
Four standard control types are included in the forms bundle:
|
||||
|
||||
* `textfield`: An area to enter plain text.
|
||||
* `select`: A drop-down list of options.
|
||||
* `checkbox`: A box which may be checked/unchecked.
|
||||
* `datetime`: An input for UTC date/time entry; gives result as a
|
||||
UNIX timestamp, in milliseconds since start of 1970, UTC.
|
||||
|
||||
New controls may be added as extensions of the `controls` category.
|
||||
Extensions of this category have two properites:
|
||||
|
||||
* `key`: The symbolic name for this control (matched against the
|
||||
`control` field in rows of the form structure).
|
||||
* `templateUrl`: The URL to the control's Angular template, relative
|
||||
to the resources directory of the bundle which exposes the extension.
|
||||
|
||||
Within the template for a control, the following variables will be
|
||||
included in scope:
|
||||
|
||||
* `ngModel`: The model where form input will be stored. Notably we
|
||||
also need to look at `field` (see below) to determine which field
|
||||
in the model should be modified.
|
||||
* `ngRequired`: True if input is required.
|
||||
* `ngPattern`: The pattern to match against (for text entry.)
|
||||
* `options`: The options for this control, as passed from the
|
||||
`options` property of an individual row.
|
||||
* `field`: Name of the field in `ngModel` which will hold the value
|
||||
for this control.
|
72
platform/forms/bundle.json
Normal file
72
platform/forms/bundle.json
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "MCT Forms",
|
||||
"description": "Form generator; includes directive and some controls.",
|
||||
"extensions": {
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctForm",
|
||||
"implementation": "MCTForm.js"
|
||||
},
|
||||
{
|
||||
"key": "mctControl",
|
||||
"implementation": "MCTControl.js",
|
||||
"depends": [ "controls[]" ]
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"key": "checkbox",
|
||||
"templateUrl": "templates/controls/checkbox.html"
|
||||
},
|
||||
{
|
||||
"key": "datetime",
|
||||
"templateUrl": "templates/controls/datetime.html"
|
||||
},
|
||||
{
|
||||
"key": "select",
|
||||
"templateUrl": "templates/controls/select.html"
|
||||
},
|
||||
{
|
||||
"key": "textfield",
|
||||
"templateUrl": "templates/controls/textfield.html"
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "DateTimeController",
|
||||
"implementation": "controllers/DateTimeController.js",
|
||||
"depends": [ "$scope" ]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
6
platform/forms/lib/moment.min.js
vendored
Normal file
6
platform/forms/lib/moment.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
platform/forms/res/templates/controls/checkbox.html
Normal file
7
platform/forms/res/templates/controls/checkbox.html
Normal file
@ -0,0 +1,7 @@
|
||||
<label class="checkbox custom no-text">
|
||||
<input type="checkbox"
|
||||
name="mctControl"
|
||||
ng-model="ngModel[field]"
|
||||
ng-disabled="ngDisabled">
|
||||
<em> </em>
|
||||
</label>
|
62
platform/forms/res/templates/controls/datetime.html
Normal file
62
platform/forms/res/templates/controls/datetime.html
Normal file
@ -0,0 +1,62 @@
|
||||
<div class='form-control complex datetime'>
|
||||
|
||||
<div class='field-hints'>
|
||||
<span class='hint date'>Date</span>
|
||||
<span class='hint time sm'>Hour</span>
|
||||
<span class='hint time sm'>Min</span>
|
||||
<span class='hint time sm'>Sec</span>
|
||||
<span class='hint timezone'>Timezone</span>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-form name="mctControl">
|
||||
<div class='fields' ng-controller="DateTimeController">
|
||||
<span class='field control date'>
|
||||
<input type='text'
|
||||
name='date'
|
||||
placeholder="YYYY-DDD"
|
||||
ng-pattern="/\d\d\d\d-\d\d\d/"
|
||||
ng-model='datetime.date'
|
||||
ng-required='true'/>
|
||||
</span>
|
||||
<span class='field control time sm'>
|
||||
<input type='text'
|
||||
name='hour'
|
||||
maxlength='2'
|
||||
min='0'
|
||||
max='23'
|
||||
integer
|
||||
ng-pattern='/\d+/'
|
||||
ng-model="datetime.hour"
|
||||
ng-required='true'/>
|
||||
</span>
|
||||
<span class='field control time sm'>
|
||||
<input type='text'
|
||||
name='min'
|
||||
maxlength='2'
|
||||
min='0'
|
||||
max='59'
|
||||
integer
|
||||
ng-pattern='/\d+/'
|
||||
ng-model="datetime.min"
|
||||
ng-required='true'/>
|
||||
</span>
|
||||
<span class='field control time sm'>
|
||||
<input type='text'
|
||||
name='sec'
|
||||
maxlength='2'
|
||||
min='0'
|
||||
max='59'
|
||||
integer
|
||||
ng-pattern='/\d+/'
|
||||
ng-model="datetime.sec"
|
||||
ng-required='true'/>
|
||||
</span>
|
||||
<span class='field control timezone'>
|
||||
UTC
|
||||
</span>
|
||||
</div>
|
||||
</ng-form>
|
||||
|
||||
|
||||
</div>
|
8
platform/forms/res/templates/controls/select.html
Normal file
8
platform/forms/res/templates/controls/select.html
Normal file
@ -0,0 +1,8 @@
|
||||
<select class='form-control input select'
|
||||
ng-model="ngModel[field]"
|
||||
ng-options="opt.value as opt.name for opt in options"
|
||||
ng-required="ngRequired"
|
||||
name="mctControl">
|
||||
<option value="" ng-if="!ngModel[field]">- Select One -</option>
|
||||
<span class='ui-symbol arw colorKey'>v</span>
|
||||
</select>
|
9
platform/forms/res/templates/controls/textfield.html
Normal file
9
platform/forms/res/templates/controls/textfield.html
Normal file
@ -0,0 +1,9 @@
|
||||
<span class='form-control shell'>
|
||||
<span class='field control'>
|
||||
<input type="text"
|
||||
ng-required="ngRequired"
|
||||
ng-model="ngModel[field]"
|
||||
ng-pattern="ngPattern"
|
||||
name="mctControl">
|
||||
</span>
|
||||
</span>
|
55
platform/forms/res/templates/form.html
Normal file
55
platform/forms/res/templates/form.html
Normal file
@ -0,0 +1,55 @@
|
||||
<form name="mctForm" novalidate>
|
||||
|
||||
<div class="form">
|
||||
<span ng-repeat="section in structure.sections">
|
||||
<div class="section-header" ng-if="section.name">
|
||||
{{section.name}}
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<ng-form name="mctFormInner" ng-repeat="row in section.rows">
|
||||
<div class="form-row validates"
|
||||
ng-class="{
|
||||
req: row.required,
|
||||
valid: mctFormInner.$dirty && mctFormInner.$valid,
|
||||
invalid: mctFormInner.$dirty && !mctFormInner.$valid
|
||||
}">
|
||||
|
||||
<div class='label' title="{{row.description}}">
|
||||
{{row.name}}
|
||||
<span ng-if="row.description"
|
||||
class="ui-symbol">
|
||||
i
|
||||
</span>
|
||||
</div>
|
||||
<div class='controls'>
|
||||
<div class="wrapper" ng-if="row.control">
|
||||
<mct-control key="row.control"
|
||||
ng-model="ngModel"
|
||||
ng-required="row.required"
|
||||
ng-pattern="getRegExp(row.pattern)"
|
||||
options="row.options"
|
||||
structure="row"
|
||||
field="row.key">
|
||||
</mct-control>
|
||||
</div>
|
||||
<div ng-repeat="item in row.items" class="validates">
|
||||
<ng-form name="mctFormItem">
|
||||
<mct-control key="item.control"
|
||||
ng-model="ngModel"
|
||||
ng-required="item.required"
|
||||
ng-pattern="getRegExp(item.pattern)"
|
||||
options="item.options"
|
||||
structure="row"
|
||||
field="item.key">
|
||||
</mct-control>
|
||||
{{item.name}}
|
||||
</ng-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</form>
|
81
platform/forms/src/MCTControl.js
Normal file
81
platform/forms/src/MCTControl.js
Normal file
@ -0,0 +1,81 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* The mct-control will dynamically include the control
|
||||
* for a form element based on a symbolic key. Individual
|
||||
* controls are defined under the extension category
|
||||
* `controls`; this allows plug-ins to introduce new form
|
||||
* control types while still making use of the form
|
||||
* generator to ensure an overall consistent form style.
|
||||
*/
|
||||
function MCTControl(controls) {
|
||||
var controlMap = {};
|
||||
|
||||
// Prepopulate controlMap for easy look up by key
|
||||
controls.forEach(function (control) {
|
||||
var path = [
|
||||
control.bundle.path,
|
||||
control.bundle.resources,
|
||||
control.templateUrl
|
||||
].join("/");
|
||||
controlMap[control.key] = path;
|
||||
});
|
||||
|
||||
function controller($scope) {
|
||||
$scope.$watch("key", function (key) {
|
||||
// Pass the template URL to ng-include via scope.
|
||||
$scope.inclusion = controlMap[key];
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// Only show at the element level
|
||||
restrict: "E",
|
||||
|
||||
// Use the included controller to populate scope
|
||||
controller: controller,
|
||||
|
||||
// Use ng-include as a template; "inclusion" will be the real
|
||||
// template path
|
||||
template: '<ng-include src="inclusion"></ng-include>',
|
||||
|
||||
// ngOptions is terminal, so we need to be higher priority
|
||||
priority: 1000,
|
||||
|
||||
// Pass through Angular's normal input field attributes
|
||||
scope: {
|
||||
// Used to choose which form control to use
|
||||
key: "=",
|
||||
|
||||
// The state of the form value itself
|
||||
ngModel: "=",
|
||||
|
||||
// Enabled/disabled state
|
||||
ngDisabled: "=",
|
||||
|
||||
// Whether or not input is required
|
||||
ngRequired: "=",
|
||||
|
||||
// Pattern (for input fields)
|
||||
ngPattern: "=",
|
||||
|
||||
// Set of choices (if any)
|
||||
options: "=",
|
||||
|
||||
// Structure (subtree of Form Structure)
|
||||
structure: "=",
|
||||
|
||||
// Name, as in "<input name="...
|
||||
field: "="
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return MCTControl;
|
||||
}
|
||||
);
|
107
platform/forms/src/MCTForm.js
Normal file
107
platform/forms/src/MCTForm.js
Normal file
@ -0,0 +1,107 @@
|
||||
/*global define,Promise*/
|
||||
|
||||
/**
|
||||
* Module defining MCTForm. Created by vwoeltje on 11/10/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"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
|
||||
* 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 form.
|
||||
* 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/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",
|
||||
|
||||
// Load the forms template
|
||||
templateUrl: templatePath,
|
||||
|
||||
// Use the controller defined above to
|
||||
// populate/respond to changes in scope
|
||||
controller: controller,
|
||||
|
||||
// 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;
|
||||
}
|
||||
);
|
48
platform/forms/src/controllers/DateTimeController.js
Normal file
48
platform/forms/src/controllers/DateTimeController.js
Normal file
@ -0,0 +1,48 @@
|
||||
/*global define,moment*/
|
||||
|
||||
define(
|
||||
["../../lib/moment.min"],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
var DATE_FORMAT = "YYYY-DDD";
|
||||
|
||||
/**
|
||||
* Controller for the `datetime` form control.
|
||||
* This is a composite control; it includes multiple
|
||||
* input fields but outputs a single timestamp (in
|
||||
* milliseconds since start of 1970) to the ngModel.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function DateTimeController($scope) {
|
||||
|
||||
// Update the
|
||||
function update() {
|
||||
var date = $scope.datetime.date,
|
||||
hour = $scope.datetime.hour,
|
||||
min = $scope.datetime.min,
|
||||
sec = $scope.datetime.sec,
|
||||
fullDateTime = moment.utc(date, DATE_FORMAT)
|
||||
.hour(hour || 0)
|
||||
.minute(min || 0)
|
||||
.second(sec || 0);
|
||||
|
||||
if (fullDateTime.isValid()) {
|
||||
$scope.ngModel[$scope.field] = fullDateTime.valueOf();
|
||||
}
|
||||
}
|
||||
|
||||
// Update value whenever any field changes.
|
||||
$scope.$watch("datetime.date", update);
|
||||
$scope.$watch("datetime.hour", update);
|
||||
$scope.$watch("datetime.min", update);
|
||||
$scope.$watch("datetime.sec", update);
|
||||
|
||||
$scope.datetime = {};
|
||||
}
|
||||
|
||||
return DateTimeController;
|
||||
|
||||
}
|
||||
);
|
60
platform/forms/test/MCTControlSpec.js
Normal file
60
platform/forms/test/MCTControlSpec.js
Normal file
@ -0,0 +1,60 @@
|
||||
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/MCTControl"],
|
||||
function (MCTControl) {
|
||||
"use strict";
|
||||
|
||||
describe("The mct-control directive", function () {
|
||||
var testControls,
|
||||
mockScope,
|
||||
mctControl;
|
||||
|
||||
beforeEach(function () {
|
||||
testControls = [
|
||||
{
|
||||
key: "abc",
|
||||
bundle: { path: "a", resources: "b" },
|
||||
templateUrl: "c/template.html"
|
||||
},
|
||||
{
|
||||
key: "xyz",
|
||||
bundle: { path: "x", resources: "y" },
|
||||
templateUrl: "z/template.html"
|
||||
}
|
||||
];
|
||||
|
||||
mockScope = jasmine.createSpyObj("$scope", [ "$watch" ]);
|
||||
|
||||
mctControl = new MCTControl(testControls);
|
||||
});
|
||||
|
||||
it("is restricted to the element level", function () {
|
||||
expect(mctControl.restrict).toEqual("E");
|
||||
});
|
||||
|
||||
it("watches its passed key to choose a template", function () {
|
||||
mctControl.controller(mockScope);
|
||||
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"key",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("changes its template dynamically", function () {
|
||||
mctControl.controller(mockScope);
|
||||
|
||||
mockScope.key = "xyz";
|
||||
mockScope.$watch.mostRecentCall.args[1]("xyz");
|
||||
|
||||
// Should have communicated the template path to
|
||||
// ng-include via the "inclusion" field in scope
|
||||
expect(mockScope.inclusion).toEqual(
|
||||
"x/y/z/template.html"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
90
platform/forms/test/MCTFormSpec.js
Normal file
90
platform/forms/test/MCTFormSpec.js
Normal file
@ -0,0 +1,90 @@
|
||||
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/MCTForm"],
|
||||
function (MCTForm) {
|
||||
"use strict";
|
||||
|
||||
describe("The mct-form directive", function () {
|
||||
var mockScope,
|
||||
mctForm;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj("$scope", [ "$watch" ]);
|
||||
mockScope.$parent = {};
|
||||
mctForm = new MCTForm();
|
||||
});
|
||||
|
||||
it("is restricted to elements", function () {
|
||||
expect(mctForm.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.
|
||||
mctForm.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";
|
||||
|
||||
mctForm.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
|
||||
mctForm.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
|
||||
mctForm.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
|
||||
mctForm.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
|
||||
mctForm.controller(mockScope);
|
||||
expect(mockScope.getRegExp()).toEqual(/\S/);
|
||||
expect(mockScope.getRegExp(undefined)).toEqual(/\S/);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
41
platform/forms/test/controllers/DateTimeControllerSpec.js
Normal file
41
platform/forms/test/controllers/DateTimeControllerSpec.js
Normal file
@ -0,0 +1,41 @@
|
||||
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/controllers/DateTimeController"],
|
||||
function (DateTimeController) {
|
||||
"use strict";
|
||||
|
||||
describe("The date-time directive", function () {
|
||||
var mockScope,
|
||||
controller;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj("$scope", [ "$watch" ]);
|
||||
controller = new DateTimeController(mockScope);
|
||||
});
|
||||
|
||||
it("watches for changes in fields", function () {
|
||||
["date", "hour", "min", "sec"].forEach(function (fieldName) {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"datetime." + fieldName,
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("converts date-time input into a timestamp", function () {
|
||||
mockScope.ngModel = {};
|
||||
mockScope.field = "test";
|
||||
mockScope.datetime.date = "2014-332";
|
||||
mockScope.datetime.hour = 22;
|
||||
mockScope.datetime.min = 55;
|
||||
mockScope.datetime.sec = 13;
|
||||
|
||||
mockScope.$watch.mostRecentCall.args[1]();
|
||||
|
||||
expect(mockScope.ngModel.test).toEqual(1417215313000);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
5
platform/forms/test/suite.json
Normal file
5
platform/forms/test/suite.json
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
"MCTControl",
|
||||
"MCTForm",
|
||||
"controllers/DateTimeController"
|
||||
]
|
Loading…
Reference in New Issue
Block a user