Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
feat(ng-model): Added ng-model-options
Browse files Browse the repository at this point in the history
Added support for the debounce part of ng-model-options

Closes #969
Closes #974
  • Loading branch information
jrote1 authored and matsko committed May 8, 2014
1 parent 5c6a18e commit f7115aa
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 43 deletions.
2 changes: 1 addition & 1 deletion example/web/hello_world.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<body hello-world-controller>

<h3>Hello {{ctrl.name}}!</h3>
name: <input type="text" ng-model="ctrl.name">
name: <input type="text" ng-model="ctrl.name" ng-model-options="{ debounce: {'default': 500, 'blur': 0} }">

<script type="application/dart" src="hello_world.dart"></script>
<script src="packages/browser/dart.js"></script>
Expand Down
7 changes: 2 additions & 5 deletions lib/core/annotation_src.dart
Original file line number Diff line number Diff line change
Expand Up @@ -547,11 +547,8 @@ abstract class DetachAware {
}

/**
* Use the @[Formatter] class annotation to register a new formatter.
*
* A formatter is a pure function that performs a transformation on input data from an expression.
* For more on formatters in Angular, see the documentation for the
* [angular:formatter](#angular-formatter) library.
* Use @[Formatter] annotation to register a new formatter. A formatter is a class
* with a [call] method (a callable function).
*
* Usage:
*
Expand Down
4 changes: 4 additions & 0 deletions lib/directive/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ library angular.directive;

import 'package:di/di.dart';
import 'dart:html' as dom;
import 'dart:convert' as convert;
import 'dart:async' as async;
import 'package:intl/intl.dart';
import 'package:angular/core/annotation.dart';
import 'package:angular/core/module_internal.dart';
Expand Down Expand Up @@ -51,6 +53,7 @@ part 'ng_non_bindable.dart';
part 'ng_model_select.dart';
part 'ng_form.dart';
part 'ng_model_validators.dart';
part 'ng_model_options.dart';

class DecoratorFormatter extends Module {
DecoratorFormatter() {
Expand Down Expand Up @@ -81,6 +84,7 @@ class DecoratorFormatter extends Module {
bind(ContentEditable, toValue: null);
bind(NgBindTypeForDateLike, toValue: null);
bind(NgModel, toValue: null);
bind(NgModelOptions, toValue: new NgModelOptions());
bind(NgValue, toValue: null);
bind(NgTrueValue, toValue: new NgTrueValue());
bind(NgFalseValue, toValue: new NgFalseValue());
Expand Down
66 changes: 39 additions & 27 deletions lib/directive/ng_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -294,26 +294,29 @@ class InputCheckbox {
final NgModel ngModel;
final NgTrueValue ngTrueValue;
final NgFalseValue ngFalseValue;
final NgModelOptions ngModelOptions;
final Scope scope;

InputCheckbox(dom.Element this.inputElement, this.ngModel,
this.scope, this.ngTrueValue, this.ngFalseValue) {
this.scope, this.ngTrueValue, this.ngFalseValue, this.ngModelOptions) {
ngModel.render = (value) {
scope.rootScope.domWrite(() {
inputElement.checked = ngTrueValue.isValue(value);
});
};
inputElement
..onChange.listen((_) {
ngModel.viewValue = inputElement.checked
? ngTrueValue.value : ngFalseValue.value;
})
..onBlur.listen((e) {
..onChange.listen((_) => ngModelOptions.executeChangeFunc(() {
ngModel.viewValue = inputElement.checked ? ngTrueValue.value : ngFalseValue.value;
}))
..onBlur.listen((_) => ngModelOptions.executeBlurFunc(() {
ngModel.markAsTouched();
});
}));
}
}




/**
* Usage:
*
Expand All @@ -337,37 +340,42 @@ class InputCheckbox {
class InputTextLike {
final dom.Element inputElement;
final NgModel ngModel;
final NgModelOptions ngModelOptions;
final Scope scope;
String _inputType;


get typedValue => (inputElement as dynamic).value;
void set typedValue(value) {
(inputElement as dynamic).value = (value == null) ? '' : value.toString();
}

InputTextLike(this.inputElement, this.ngModel, this.scope) {
InputTextLike(this.inputElement, this.ngModel, this.scope, this.ngModelOptions) {
ngModel.render = (value) {
scope.rootScope.domWrite(() {
if (value == null) value = '';

var currentValue = typedValue;
if (value != currentValue && !(value is num && currentValue is num &&
value.isNaN && currentValue.isNaN)) {
typedValue = value;
typedValue = value;
}
});
};

inputElement
..onChange.listen(processValue)
..onInput.listen(processValue)
..onBlur.listen((e) {
..onChange.listen((event) => ngModelOptions.executeChangeFunc(() => processValue(event)))
..onInput.listen((event) => ngModelOptions.executeInputFunc(() => processValue(event)))
..onBlur.listen((_) => ngModelOptions.executeBlurFunc(() {
ngModel.markAsTouched();
});
}));
}

void processValue([_]) {
var value = typedValue;

if (value != ngModel.viewValue) ngModel.viewValue = value;

ngModel.validate();
}
}
Expand All @@ -394,6 +402,7 @@ class InputTextLike {
class InputNumberLike {
final dom.InputElement inputElement;
final NgModel ngModel;
final NgModelOptions ngModelOptions;
final Scope scope;


Expand All @@ -414,7 +423,7 @@ class InputNumberLike {
}
}

InputNumberLike(dom.Element this.inputElement, this.ngModel, this.scope) {
InputNumberLike(dom.Element this.inputElement, this.ngModel, this.scope, this.ngModelOptions) {
ngModel.render = (value) {
scope.rootScope.domWrite(() {
if (value != typedValue
Expand All @@ -424,11 +433,11 @@ class InputNumberLike {
});
};
inputElement
..onChange.listen(relaxFnArgs(processValue))
..onInput.listen(relaxFnArgs(processValue))
..onBlur.listen((e) {
..onChange.listen((event) => ngModelOptions.executeChangeFunc(() => processValue()))
..onInput.listen((event) => ngModelOptions.executeInputFunc(() => processValue()))
..onBlur.listen((_) => ngModelOptions.executeBlurFunc(() {
ngModel.markAsTouched();
});
}));
}

void processValue() {
Expand Down Expand Up @@ -586,11 +595,12 @@ class InputDateLike {
toFactory: (Injector i) => new NgBindTypeForDateLike(i.get(dom.Element)));
final dom.InputElement inputElement;
final NgModel ngModel;
final NgModelOptions ngModelOptions;
final Scope scope;
NgBindTypeForDateLike ngBindType;

InputDateLike(dom.Element this.inputElement, this.ngModel, this.scope,
this.ngBindType) {
this.ngBindType, this.ngModelOptions) {
if (inputElement.type == 'datetime-local') {
ngBindType.idlAttrKind = NgBindTypeForDateLike.NUMBER;
}
Expand All @@ -600,11 +610,11 @@ class InputDateLike {
});
};
inputElement
..onChange.listen(relaxFnArgs(processValue))
..onInput.listen(relaxFnArgs(processValue))
..onBlur.listen((e) {
..onChange.listen((event) => ngModelOptions.executeChangeFunc(() => processValue()))
..onInput.listen((event) => ngModelOptions.executeInputFunc(() => processValue()))
..onBlur.listen((_) => ngModelOptions.executeBlurFunc(() {
ngModel.markAsTouched();
});
}));
}

dynamic get typedValue => ngBindType.inputTypedValue;
Expand Down Expand Up @@ -680,7 +690,9 @@ class NgValue {
NgValue(this.element);

@NgOneWay('ng-value')
void set value(val) { this._value = val; }
void set value(val) {
this._value = val;
}
dynamic get value => _value == null ? (element as dynamic).value : _value;
}

Expand Down Expand Up @@ -767,7 +779,7 @@ class InputRadio {
..onClick.listen((_) {
if (radioButtonElement.checked) ngModel.viewValue = ngValue.value;
})
..onBlur.listen((e) {
..onBlur.listen((event) {
ngModel.markAsTouched();
});
}
Expand All @@ -785,8 +797,8 @@ class InputRadio {
*/
@Decorator(selector: '[contenteditable][ng-model]')
class ContentEditable extends InputTextLike {
ContentEditable(dom.Element inputElement, NgModel ngModel, Scope scope)
: super(inputElement, ngModel, scope);
ContentEditable(dom.Element inputElement, NgModel ngModel, Scope scope, NgModelOptions modelOptions)
: super(inputElement, ngModel, scope, modelOptions);

// The implementation is identical to InputTextLike but use innerHtml instead of value
String get typedValue => (inputElement as dynamic).innerHtml;
Expand Down
62 changes: 62 additions & 0 deletions lib/directive/ng_model_options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
part of angular.directive;

@Decorator(
selector: 'input[ng-model-options]',
map: const {'ng-model-options': '=>options'})
class NgModelOptions {
static const String _DEBOUNCE_DEFAULT_KEY = "default";
static const String _DEBOUNCE_BLUR_KEY = "blur";
static const String _DEBOUNCE_CHANGE_KEY = "change";
static const String _DEBOUNCE_INPUT_KEY = "input";

int _debounceDefaultValue = 0;
int _debounceBlurValue;
int _debounceChangeValue;
int _debounceInputValue;

async.Timer _blurTimer;
async.Timer _changeTimer;
async.Timer _inputTimer;

NgModelOptions();

void set options(options) {
if (options["debounce"] is int){
_debounceDefaultValue = options["debounce"];
} else {
Map debounceOptions = options["debounce"];
if (debounceOptions.containsKey(_DEBOUNCE_DEFAULT_KEY)){
_debounceDefaultValue = debounceOptions[_DEBOUNCE_DEFAULT_KEY];
}
_debounceBlurValue = debounceOptions[_DEBOUNCE_BLUR_KEY];
_debounceChangeValue = debounceOptions[_DEBOUNCE_CHANGE_KEY];
_debounceInputValue = debounceOptions[_DEBOUNCE_INPUT_KEY];
}
}

void executeBlurFunc(func()) {
var delay = _debounceBlurValue == null ? _debounceDefaultValue : _debounceBlurValue;
_blurTimer = _runFuncDebounced(delay, func, _blurTimer);
}

void executeChangeFunc(func()) {
var delay = _debounceChangeValue == null ? _debounceDefaultValue : _debounceChangeValue;
_changeTimer = _runFuncDebounced(delay, func, _changeTimer);
}

void executeInputFunc(func()) {
var delay = _debounceInputValue == null ? _debounceDefaultValue : _debounceInputValue;
_inputTimer = _runFuncDebounced(delay, func, _inputTimer);
}

async.Timer _runFuncDebounced(int delay, func(), async.Timer timer){
if (timer != null && timer.isActive) timer.cancel();

if (delay == 0){
func();
return null;
} else {
return new async.Timer(new Duration(milliseconds: delay), func);
}
}
}
1 change: 1 addition & 0 deletions test/angular_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ main() {
"angular.directive.NgIf",
"angular.directive.NgInclude",
"angular.directive.NgModel",
"angular.directive.NgModelOptions",
"angular.directive.NgModelConverter",
"angular.directive.NgModelEmailValidator",
"angular.directive.NgModelMaxLengthValidator",
Expand Down
Loading

0 comments on commit f7115aa

Please sign in to comment.