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

Commit 8024a57

Browse files
committed
doc(NgModelController) add example and $render documentation
Closes#930
1 parent 073e76f commit 8024a57

File tree

3 files changed

+107
-10
lines changed

3 files changed

+107
-10
lines changed

docs/content/guide/dev_guide.forms.ngdoc renamed to docs/content/guide/forms.ngdoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
@ngdoc overview
2-
@name Developer Guide: Forms
2+
@name Forms
33
@description
44

55
Controls (`input`, `select`, `textarea`) are a way for user to enter data.

src/ng/directive/input.js

+92-7
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
628628
/**
629629
* @ngdoc directive
630630
* @name angular.module.ng.$compileProvider.directive.textarea
631+
* @restrict E
631632
*
632633
* @description
633634
* HTML textarea element control with angular data-binding. The data-binding and validation
@@ -782,6 +783,79 @@ var VALID_CLASS = 'ng-valid',
782783
*
783784
* @description
784785
*
786+
* `NgModelController` provides API for the `ng-model` directive. The controller contains
787+
* services for data-binding, validation, CSS update, value formatting and parsing. It
788+
* specifically does not contain any logic which deals with DOM rendering or listening to
789+
* DOM events. The `NgModelController` is meant to be extended by other directives where, the
790+
* directive provides DOM manipulation and the `NgModelController` provides the data-binding.
791+
*
792+
* This example shows how to use `NgModelController` with a custom control to achieve
793+
* data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
794+
* collaborate together to achieve the desired result.
795+
*
796+
* <example module="customControl">
797+
<file name="style.css">
798+
[contenteditable] {
799+
border: 1px solid black;
800+
background-color: white;
801+
min-height: 20px;
802+
}
803+
804+
.ng-invalid {
805+
border: 1px solid red;
806+
}
807+
808+
</file>
809+
<file name="script.js">
810+
angular.module('customControl', []).
811+
directive('contenteditable', function() {
812+
return {
813+
restrict: 'A', // only activate on element attribute
814+
require: '?ngModel', // get a hold of NgModelController
815+
link: function(scope, element, attrs, ngModel) {
816+
if(!ngModel) return; // do nothing if no ng-model
817+
818+
// Specify how UI should be updated
819+
ngModel.$render = function() {
820+
element.html(ngModel.$viewValue || '');
821+
};
822+
823+
// Listen for change events to enable binding
824+
element.bind('blur keyup change', function() {
825+
scope.$apply(read);
826+
});
827+
read(); // initialize
828+
829+
// Write data to the model
830+
function read() {
831+
ngModel.$setViewValue(element.html());
832+
}
833+
}
834+
};
835+
});
836+
</file>
837+
<file name="index.html">
838+
<form name="myForm">
839+
<div contenteditable
840+
name="myWidget" ng-model="userContent"
841+
required>Change me!</div>
842+
<span ng-show="myForm.myWidget.$error.required">Required!</span>
843+
<hr>
844+
<textarea ng-model="userContent"></textarea>
845+
</form>
846+
</file>
847+
<file name="scenario.js">
848+
it('should data-bind and become invalid', function() {
849+
var contentEditable = element('[contenteditable]');
850+
851+
expect(contentEditable.text()).toEqual('Change me!');
852+
input('userContent').enter('');
853+
expect(contentEditable.text()).toEqual('');
854+
expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
855+
});
856+
</file>
857+
* </example>
858+
*
785859
*/
786860
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$element',
787861
function($scope, $exceptionHandler, $attr, ngModel, $element) {
@@ -794,9 +868,19 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e
794868
this.$dirty = false;
795869
this.$valid = true;
796870
this.$invalid = false;
797-
this.$render = noop;
798871
this.$name = $attr.name;
799872

873+
/**
874+
* @ngdoc function
875+
* @name angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$render
876+
* @methodOf angular.module.ng.$compileProvider.directive.ngModel.NgModelController
877+
*
878+
* @description
879+
* Called when the view needs to be updated. It is expected that the user of the ng-model
880+
* directive will implement this method.
881+
*/
882+
this.$render = noop;
883+
800884
var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
801885
invalidCount = 0, // used to easily determine if we are valid
802886
$error = this.$error = {}; // keep invalid keys here
@@ -958,7 +1042,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e
9581042
* - {@link angular.module.ng.$compileProvider.directive.textarea textarea}
9591043
*
9601044
*/
961-
var ngModelDirective = [function() {
1045+
var ngModelDirective = function() {
9621046
return {
9631047
inject: {
9641048
ngModel: 'accessor'
@@ -978,7 +1062,7 @@ var ngModelDirective = [function() {
9781062
});
9791063
}
9801064
};
981-
}];
1065+
};
9821066

9831067

9841068
/**
@@ -1039,11 +1123,12 @@ var ngChangeDirective = valueFn({
10391123
});
10401124

10411125

1042-
var requiredDirective = [function() {
1126+
var requiredDirective = function() {
10431127
return {
10441128
require: '?ngModel',
10451129
link: function(scope, elm, attr, ctrl) {
10461130
if (!ctrl) return;
1131+
attr.required = true; // force truthy in case we are on non input element
10471132

10481133
var validator = function(value) {
10491134
if (attr.required && (isEmpty(value) || value === false)) {
@@ -1063,7 +1148,7 @@ var requiredDirective = [function() {
10631148
});
10641149
}
10651150
};
1066-
}];
1151+
};
10671152

10681153

10691154
/**
@@ -1144,7 +1229,7 @@ var ngListDirective = function() {
11441229

11451230
var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
11461231

1147-
var ngValueDirective = [function() {
1232+
var ngValueDirective = function() {
11481233
return {
11491234
priority: 100,
11501235
compile: function(tpl, tplAttr) {
@@ -1162,4 +1247,4 @@ var ngValueDirective = [function() {
11621247
}
11631248
}
11641249
};
1165-
}];
1250+
};

test/ng/directive/inputSpec.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,9 @@ describe('input', function() {
285285
var formElm, inputElm, scope, $compile, changeInputValueTo;
286286

287287
function compileInput(inputHtml) {
288-
formElm = jqLite('<form name="form">' + inputHtml + '</form>');
289-
inputElm = formElm.find('input');
288+
inputElm = jqLite(inputHtml);
289+
formElm = jqLite('<form name="form"></form>');
290+
formElm.append(inputElm);
290291
$compile(formElm)(scope);
291292
}
292293

@@ -633,6 +634,17 @@ describe('input', function() {
633634
expect(inputElm.val()).toBe('0')
634635
expect(scope.form.alias.$error.required).toBeFalsy();
635636
});
637+
638+
it('should register required on non boolean elements', function() {
639+
compileInput('<div ng-model="value" name="alias" required>');
640+
641+
scope.$apply(function() {
642+
scope.value = '';
643+
});
644+
645+
expect(inputElm).toBeInvalid();
646+
expect(scope.form.alias.$error.required).toBeTruthy();
647+
});
636648
});
637649
});
638650

0 commit comments

Comments
 (0)