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

Commit

Permalink
refactor(forms): Even better forms
Browse files Browse the repository at this point in the history
- 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)
  • Loading branch information
vojtajina committed Feb 29, 2012
1 parent e23fa76 commit 21c725f
Show file tree
Hide file tree
Showing 18 changed files with 2,233 additions and 2,109 deletions.
1 change: 0 additions & 1 deletion angularFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ angularFiles = {
'src/service/filter/filters.js',
'src/service/filter/limitTo.js',
'src/service/filter/orderBy.js',
'src/service/formFactory.js',
'src/service/interpolate.js',
'src/service/location.js',
'src/service/log.js',
Expand Down
58 changes: 0 additions & 58 deletions docs/content/api/angular.inputType.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,61 +32,3 @@ All `inputType` widgets support:
- **`ng:pattern`** Sets `PATTERN` validation error key if the value does not match the
RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
patterns defined as scope expressions.



# Example

<doc:example>
<doc:source>
<script>
angular.inputType('json', function(element, scope) {
scope.$parseView = function() {
try {
this.$modelValue = angular.fromJson(this.$viewValue);
if (this.$error.JSON) {
this.$emit('$valid', 'JSON');
}
} catch (e) {
this.$emit('$invalid', 'JSON');
}
}

scope.$parseModel = function() {
this.$viewValue = angular.toJson(this.$modelValue);
}
});

function Ctrl($scope) {
$scope.data = {
framework:'angular',
codenames:'supper-powers'
}
$scope.required = false;
$scope.disabled = false;
$scope.readonly = false;
}
</script>
<div ng:controller="Ctrl">
<form name="myForm">
<input type="json" ng:model="data" size="80"
ng:required="{{required}}" ng:disabled="{{disabled}}"
ng:readonly="{{readonly}}"/><br/>
Required: <input type="checkbox" ng:model="required"> <br/>
Disabled: <input type="checkbox" ng:model="disabled"> <br/>
Readonly: <input type="checkbox" ng:model="readonly"> <br/>
<pre>data={{data}}</pre>
<pre>myForm={{myForm}}</pre>
</form>
</div>
</doc:source>
<doc:scenario>
it('should invalidate on wrong input', function() {
expect(element('form[name=myForm]').prop('className')).toMatch('ng-valid');
input('data').enter('{}');
expect(binding('data')).toEqual('{}');
input('data').enter('{');
expect(element('form[name=myForm]').prop('className')).toMatch('ng-invalid');
});
</doc:scenario>
</doc:example>
2 changes: 1 addition & 1 deletion docs/content/cookbook/advancedform.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ detection, and preventing invalid form submission.
};

$scope.isSaveDisabled = function() {
return $scope.myForm.$invalid || angular.equals(master, $scope.form);
return $scope.myForm.invalid || angular.equals(master, $scope.form);
};

$scope.cancel();
Expand Down
82 changes: 32 additions & 50 deletions docs/content/guide/dev_guide.forms.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ The following example demonstrates:
};

$scope.isSaveDisabled = function() {
return $scope.userForm.$invalid || angular.equals($scope.master, $scope.form);
return $scope.userForm.invalid || angular.equals($scope.master, $scope.form);
};

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

<label>Name:</label><br/>
<input type="text" name="customer" ng:model="form.customer" required/>
<span class="error" ng:show="userForm.customer.$error.REQUIRED">
<span class="error" ng:show="userForm.customer.error.REQUIRED">
Customer name is required!</span>
<br/><br/>

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

<span class="error" ng:show="addressForm.$invalid">
<span class="error" ng:show="addressForm.invalid">
Incomplete address:
<span class="error" ng:show="addressForm.state.$error.REQUIRED">
<span class="error" ng:show="addressForm.state.error.REQUIRED">
Missing state!</span>
<span class="error" ng:show="addressForm.state.$error.PATTERN">
<span class="error" ng:show="addressForm.state.error.PATTERN">
Invalid state!</span>
<span class="error" ng:show="addressForm.zip.$error.REQUIRED">
<span class="error" ng:show="addressForm.zip.error.REQUIRED">
Missing zip!</span>
<span class="error" ng:show="addressForm.zip.$error.PATTERN">
<span class="error" ng:show="addressForm.zip.error.PATTERN">
Invalid zip!</span>
</span>
</ng:form>
Expand Down Expand Up @@ -284,56 +284,38 @@ This example shows how to implement a custom HTML editor widget in Angular.
$scope.htmlContent = '<b>Hello</b> <i>World</i>!';
}

HTMLEditorWidget.$inject = ['$scope', '$element', '$sanitize'];
function HTMLEditorWidget(scope, element, $sanitize) {
scope.$parseModel = function() {
// need to protect for script injection
try {
scope.$viewValue = $sanitize(
scope.$modelValue || '');
if (this.$error.HTML) {
// we were invalid, but now we are OK.
scope.$emit('$valid', 'HTML');
}
} catch (e) {
// if HTML not parsable invalidate form.
scope.$emit('$invalid', 'HTML');
}
}
angular.module('formModule', []).directive('ngHtmlEditor', function ($sanitize) {
return {
require: 'ngModel',
link: function(scope, elm, attr, ctrl) {
attr.$set('contentEditable', true);

scope.$render = function() {
element.html(this.$viewValue);
}
ctrl.$render = function() {
elm.html(ctrl.viewValue);
};

element.bind('keyup', function() {
scope.$apply(function() {
scope.$emit('$viewChange', element.html());
});
});
}
ctrl.formatters.push(function(value) {
try {
value = $sanitize(value || '');
ctrl.emitValidity('HTML', true);
} catch (e) {
ctrl.emitValidity('HTML', false);
}

});

angular.module('formModule', [], function($compileProvider){
$compileProvider.directive('ngHtmlEditorModel', function ($formFactory) {
return function(scope, element, attr) {
var form = $formFactory.forElement(element),
widget;
element.attr('contentEditable', true);
widget = form.$createWidget({
scope: scope,
model: attr.ngHtmlEditorModel,
controller: HTMLEditorWidget,
controllerArgs: {$element: element}});
// if the element is destroyed, then we need to
// notify the form.
element.bind('$destroy', function() {
widget.$destroy();
elm.bind('keyup', function() {
scope.$apply(function() {
ctrl.read(elm.html());
});
});
};
});

}
};
});
</script>
<form name='editorForm' ng:controller="EditorCntl">
<div ng:html-editor-model="htmlContent"></div>
<div ng:html-editor ng:model="htmlContent"></div>
<hr/>
HTML: <br/>
<textarea ng:model="htmlContent" cols="80"></textarea>
Expand Down
2 changes: 1 addition & 1 deletion docs/src/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@

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

<ul id="content-list" ng:class="sectionId" ng:cloak>
<li ng:repeat="page in pages | filter:search" ng:class="getClass(page)">
Expand Down
12 changes: 0 additions & 12 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ var $boolean = 'boolean',
angular = window.angular || (window.angular = {}),
angularModule,
/** @name angular.module.ng */
angularInputType = extensionMap(angular, 'inputType', lowercase),
nodeName_,
uid = ['0', '0', '0'],
DATE_ISOSTRING_LN = 24;
Expand Down Expand Up @@ -272,17 +271,6 @@ identity.$inject = [];

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

function extensionMap(angular, name, transform) {
var extPoint;
return angular[name] || (extPoint = angular[name] = function(name, fn, prop){
name = (transform || identity)(name);
if (isDefined(fn)) {
extPoint[name] = extend(fn, prop || {});
}
return extPoint[name];
});
}

/**
* @ngdoc function
* @name angular.isUndefined
Expand Down
9 changes: 7 additions & 2 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,13 @@ function publishExternalAPI(angular){
ngSwitchDefault: ngSwitchDefaultDirective,
ngOptions: ngOptionsDirective,
ngView: ngViewDirective,
ngTransclude: ngTranscludeDirective
ngTransclude: ngTranscludeDirective,
ngModel: ngModelDirective,
ngList: ngListDirective,
ngChange: ngChangeDirective,
ngBindImmediate: ngBindImmediateDirective,
required: requiredDirective,
ngRequired: requiredDirective
}).
directive(ngEventDirectives).
directive(ngAttributeAliasDirectives);
Expand All @@ -110,7 +116,6 @@ function publishExternalAPI(angular){
$provide.service('$exceptionHandler', $ExceptionHandlerProvider);
$provide.service('$filter', $FilterProvider);
$provide.service('$interpolate', $InterpolateProvider);
$provide.service('$formFactory', $FormFactoryProvider);
$provide.service('$http', $HttpProvider);
$provide.service('$httpBackend', $HttpBackendProvider);
$provide.service('$location', $LocationProvider);
Expand Down
2 changes: 1 addition & 1 deletion src/scenario/Scenario.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ function browserTrigger(element, type, keys) {
(function(fn){
var parentTrigger = fn.trigger;
fn.trigger = function(type) {
if (/(click|change|keydown)/.test(type)) {
if (/(click|change|keydown|blur)/.test(type)) {
var processDefaults = [];
this.each(function(index, node) {
processDefaults.push(browserTrigger(node, type));
Expand Down
2 changes: 1 addition & 1 deletion src/scenario/dsl.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ angular.scenario.dsl('input', function() {
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'", function($window, $document, done) {
var input = $document.elements('[ng\\:model="$1"]', this.name).filter(':input');
input.val(value);
input.trigger('keydown');
input.trigger('blur');
done();
});
};
Expand Down
Loading

0 comments on commit 21c725f

Please sign in to comment.