Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 21c725f

Browse files
committed
refactor(forms): Even better forms
- remove $formFactory completely - remove parallel scope hierarchy (forms, widgets) - use new compiler features (widgets, forms are controllers) - any directive can add formatter/parser (validators, convertors) Breaks no custom input types Breaks removed integer input type Breaks remove list input type (ng-list directive instead) Breaks inputs bind only blur event by default (added ng:bind-change directive)
1 parent e23fa76 commit 21c725f

18 files changed

+2233
-2109
lines changed

angularFiles.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ angularFiles = {
2323
'src/service/filter/filters.js',
2424
'src/service/filter/limitTo.js',
2525
'src/service/filter/orderBy.js',
26-
'src/service/formFactory.js',
2726
'src/service/interpolate.js',
2827
'src/service/location.js',
2928
'src/service/log.js',

docs/content/api/angular.inputType.ngdoc

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -32,61 +32,3 @@ All `inputType` widgets support:
3232
- **`ng:pattern`** Sets `PATTERN` validation error key if the value does not match the
3333
RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
3434
patterns defined as scope expressions.
35-
36-
37-
38-
# Example
39-
40-
<doc:example>
41-
<doc:source>
42-
<script>
43-
angular.inputType('json', function(element, scope) {
44-
scope.$parseView = function() {
45-
try {
46-
this.$modelValue = angular.fromJson(this.$viewValue);
47-
if (this.$error.JSON) {
48-
this.$emit('$valid', 'JSON');
49-
}
50-
} catch (e) {
51-
this.$emit('$invalid', 'JSON');
52-
}
53-
}
54-
55-
scope.$parseModel = function() {
56-
this.$viewValue = angular.toJson(this.$modelValue);
57-
}
58-
});
59-
60-
function Ctrl($scope) {
61-
$scope.data = {
62-
framework:'angular',
63-
codenames:'supper-powers'
64-
}
65-
$scope.required = false;
66-
$scope.disabled = false;
67-
$scope.readonly = false;
68-
}
69-
</script>
70-
<div ng:controller="Ctrl">
71-
<form name="myForm">
72-
<input type="json" ng:model="data" size="80"
73-
ng:required="{{required}}" ng:disabled="{{disabled}}"
74-
ng:readonly="{{readonly}}"/><br/>
75-
Required: <input type="checkbox" ng:model="required"> <br/>
76-
Disabled: <input type="checkbox" ng:model="disabled"> <br/>
77-
Readonly: <input type="checkbox" ng:model="readonly"> <br/>
78-
<pre>data={{data}}</pre>
79-
<pre>myForm={{myForm}}</pre>
80-
</form>
81-
</div>
82-
</doc:source>
83-
<doc:scenario>
84-
it('should invalidate on wrong input', function() {
85-
expect(element('form[name=myForm]').prop('className')).toMatch('ng-valid');
86-
input('data').enter('{}');
87-
expect(binding('data')).toEqual('{}');
88-
input('data').enter('{');
89-
expect(element('form[name=myForm]').prop('className')).toMatch('ng-invalid');
90-
});
91-
</doc:scenario>
92-
</doc:example>

docs/content/cookbook/advancedform.ngdoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ detection, and preventing invalid form submission.
5252
};
5353

5454
$scope.isSaveDisabled = function() {
55-
return $scope.myForm.$invalid || angular.equals(master, $scope.form);
55+
return $scope.myForm.invalid || angular.equals(master, $scope.form);
5656
};
5757

5858
$scope.cancel();

docs/content/guide/dev_guide.forms.ngdoc

Lines changed: 32 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ The following example demonstrates:
138138
};
139139

140140
$scope.isSaveDisabled = function() {
141-
return $scope.userForm.$invalid || angular.equals($scope.master, $scope.form);
141+
return $scope.userForm.invalid || angular.equals($scope.master, $scope.form);
142142
};
143143

144144
$scope.cancel();
@@ -150,7 +150,7 @@ The following example demonstrates:
150150

151151
<label>Name:</label><br/>
152152
<input type="text" name="customer" ng:model="form.customer" required/>
153-
<span class="error" ng:show="userForm.customer.$error.REQUIRED">
153+
<span class="error" ng:show="userForm.customer.error.REQUIRED">
154154
Customer name is required!</span>
155155
<br/><br/>
156156

@@ -165,15 +165,15 @@ The following example demonstrates:
165165
<input type="text" name="zip" ng:pattern="zip" size="5" required
166166
ng:model="form.address.zip"/><br/><br/>
167167

168-
<span class="error" ng:show="addressForm.$invalid">
168+
<span class="error" ng:show="addressForm.invalid">
169169
Incomplete address:
170-
<span class="error" ng:show="addressForm.state.$error.REQUIRED">
170+
<span class="error" ng:show="addressForm.state.error.REQUIRED">
171171
Missing state!</span>
172-
<span class="error" ng:show="addressForm.state.$error.PATTERN">
172+
<span class="error" ng:show="addressForm.state.error.PATTERN">
173173
Invalid state!</span>
174-
<span class="error" ng:show="addressForm.zip.$error.REQUIRED">
174+
<span class="error" ng:show="addressForm.zip.error.REQUIRED">
175175
Missing zip!</span>
176-
<span class="error" ng:show="addressForm.zip.$error.PATTERN">
176+
<span class="error" ng:show="addressForm.zip.error.PATTERN">
177177
Invalid zip!</span>
178178
</span>
179179
</ng:form>
@@ -284,56 +284,38 @@ This example shows how to implement a custom HTML editor widget in Angular.
284284
$scope.htmlContent = '<b>Hello</b> <i>World</i>!';
285285
}
286286

287-
HTMLEditorWidget.$inject = ['$scope', '$element', '$sanitize'];
288-
function HTMLEditorWidget(scope, element, $sanitize) {
289-
scope.$parseModel = function() {
290-
// need to protect for script injection
291-
try {
292-
scope.$viewValue = $sanitize(
293-
scope.$modelValue || '');
294-
if (this.$error.HTML) {
295-
// we were invalid, but now we are OK.
296-
scope.$emit('$valid', 'HTML');
297-
}
298-
} catch (e) {
299-
// if HTML not parsable invalidate form.
300-
scope.$emit('$invalid', 'HTML');
301-
}
302-
}
287+
angular.module('formModule', []).directive('ngHtmlEditor', function ($sanitize) {
288+
return {
289+
require: 'ngModel',
290+
link: function(scope, elm, attr, ctrl) {
291+
attr.$set('contentEditable', true);
303292

304-
scope.$render = function() {
305-
element.html(this.$viewValue);
306-
}
293+
ctrl.$render = function() {
294+
elm.html(ctrl.viewValue);
295+
};
307296

308-
element.bind('keyup', function() {
309-
scope.$apply(function() {
310-
scope.$emit('$viewChange', element.html());
311-
});
312-
});
313-
}
297+
ctrl.formatters.push(function(value) {
298+
try {
299+
value = $sanitize(value || '');
300+
ctrl.emitValidity('HTML', true);
301+
} catch (e) {
302+
ctrl.emitValidity('HTML', false);
303+
}
304+
305+
});
314306

315-
angular.module('formModule', [], function($compileProvider){
316-
$compileProvider.directive('ngHtmlEditorModel', function ($formFactory) {
317-
return function(scope, element, attr) {
318-
var form = $formFactory.forElement(element),
319-
widget;
320-
element.attr('contentEditable', true);
321-
widget = form.$createWidget({
322-
scope: scope,
323-
model: attr.ngHtmlEditorModel,
324-
controller: HTMLEditorWidget,
325-
controllerArgs: {$element: element}});
326-
// if the element is destroyed, then we need to
327-
// notify the form.
328-
element.bind('$destroy', function() {
329-
widget.$destroy();
307+
elm.bind('keyup', function() {
308+
scope.$apply(function() {
309+
ctrl.read(elm.html());
310+
});
330311
});
331-
};
332-
});
312+
313+
}
314+
};
333315
});
334316
</script>
335317
<form name='editorForm' ng:controller="EditorCntl">
336-
<div ng:html-editor-model="htmlContent"></div>
318+
<div ng:html-editor ng:model="htmlContent"></div>
337319
<hr/>
338320
HTML: <br/>
339321
<textarea ng:model="htmlContent" cols="80"></textarea>

docs/src/templates/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101

102102
<div id="sidebar">
103103
<input type="text" ng:model="search" id="search-box" placeholder="search the docs"
104-
tabindex="1" accesskey="s">
104+
tabindex="1" accesskey="s" ng:bind-immediate>
105105

106106
<ul id="content-list" ng:class="sectionId" ng:cloak>
107107
<li ng:repeat="page in pages | filter:search" ng:class="getClass(page)">

src/Angular.js

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ var $boolean = 'boolean',
9191
angular = window.angular || (window.angular = {}),
9292
angularModule,
9393
/** @name angular.module.ng */
94-
angularInputType = extensionMap(angular, 'inputType', lowercase),
9594
nodeName_,
9695
uid = ['0', '0', '0'],
9796
DATE_ISOSTRING_LN = 24;
@@ -272,17 +271,6 @@ identity.$inject = [];
272271

273272
function valueFn(value) {return function() {return value;};}
274273

275-
function extensionMap(angular, name, transform) {
276-
var extPoint;
277-
return angular[name] || (extPoint = angular[name] = function(name, fn, prop){
278-
name = (transform || identity)(name);
279-
if (isDefined(fn)) {
280-
extPoint[name] = extend(fn, prop || {});
281-
}
282-
return extPoint[name];
283-
});
284-
}
285-
286274
/**
287275
* @ngdoc function
288276
* @name angular.isUndefined

src/AngularPublic.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,13 @@ function publishExternalAPI(angular){
9898
ngSwitchDefault: ngSwitchDefaultDirective,
9999
ngOptions: ngOptionsDirective,
100100
ngView: ngViewDirective,
101-
ngTransclude: ngTranscludeDirective
101+
ngTransclude: ngTranscludeDirective,
102+
ngModel: ngModelDirective,
103+
ngList: ngListDirective,
104+
ngChange: ngChangeDirective,
105+
ngBindImmediate: ngBindImmediateDirective,
106+
required: requiredDirective,
107+
ngRequired: requiredDirective
102108
}).
103109
directive(ngEventDirectives).
104110
directive(ngAttributeAliasDirectives);
@@ -110,7 +116,6 @@ function publishExternalAPI(angular){
110116
$provide.service('$exceptionHandler', $ExceptionHandlerProvider);
111117
$provide.service('$filter', $FilterProvider);
112118
$provide.service('$interpolate', $InterpolateProvider);
113-
$provide.service('$formFactory', $FormFactoryProvider);
114119
$provide.service('$http', $HttpProvider);
115120
$provide.service('$httpBackend', $HttpBackendProvider);
116121
$provide.service('$location', $LocationProvider);

src/scenario/Scenario.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ function browserTrigger(element, type, keys) {
327327
(function(fn){
328328
var parentTrigger = fn.trigger;
329329
fn.trigger = function(type) {
330-
if (/(click|change|keydown)/.test(type)) {
330+
if (/(click|change|keydown|blur)/.test(type)) {
331331
var processDefaults = [];
332332
this.each(function(index, node) {
333333
processDefaults.push(browserTrigger(node, type));

src/scenario/dsl.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ angular.scenario.dsl('input', function() {
203203
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'", function($window, $document, done) {
204204
var input = $document.elements('[ng\\:model="$1"]', this.name).filter(':input');
205205
input.val(value);
206-
input.trigger('keydown');
206+
input.trigger('blur');
207207
done();
208208
});
209209
};

0 commit comments

Comments
 (0)