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

feat(ng-model-options) Added ng-model-options #974

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ packages:
barback:
description: barback
source: hosted
version: "0.13.0"
version: "0.12.0"
browser:
description: browser
source: hosted
Expand Down
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
6 changes: 5 additions & 1 deletion 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,10 +53,11 @@ 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() {
bind(AHref, toValue: null);
bind(AHref, toValue: null);
bind(NgBaseCss); // The root injector should have an empty NgBaseCss
bind(NgBind, toValue: null);
bind(NgBindTemplate, toValue: null);
Expand All @@ -81,6 +84,7 @@ class DecoratorFormatter extends Module {
bind(ContentEditable, toValue: null);
bind(NgBindTypeForDateLike, toValue: null);
bind(NgModel, toValue: null);
bind(NgModelOptions, toValue: null);
bind(NgValue, toValue: null);
bind(NgTrueValue, toValue: new NgTrueValue());
bind(NgFalseValue, toValue: new NgFalseValue());
Expand Down
79 changes: 46 additions & 33 deletions lib/directive/ng_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,7 @@ class NgModel extends NgControl implements AttachAware {
onChange(changeRecord is CollectionChangeRecord
? changeRecord.iterable
: changeRecord);
},
collection: true);
}, collection: true);
} else if (_expression != null) {
_watch = _scope.watch(_expression, onChange);
}
Expand Down Expand Up @@ -294,26 +293,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 +339,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 +401,7 @@ class InputTextLike {
class InputNumberLike {
final dom.InputElement inputElement;
final NgModel ngModel;
final NgModelOptions ngModelOptions;
final Scope scope;


Expand All @@ -414,21 +422,20 @@ 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
&& (value == null || value is num && !value.isNaN)) {
if (value != typedValue && (value == null || value is num && !value.isNaN)) {
typedValue = value;
}
});
};
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((event) => ngModelOptions.executeBlurFunc(() => () {
ngModel.markAsTouched();
});
}));
}

void processValue() {
Expand Down Expand Up @@ -484,9 +491,12 @@ class NgBindTypeForDateLike {

dynamic get inputTypedValue {
switch (idlAttrKind) {
case DATE: return inputValueAsDate;
case NUMBER: return inputElement.valueAsNumber;
default: return inputElement.value;
case DATE:
return inputValueAsDate;
case NUMBER:
return inputElement.valueAsNumber;
default:
return inputElement.value;
}
}

Expand Down Expand Up @@ -586,11 +596,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 +611,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 +691,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 @@ -785,8 +798,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]')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some doc about the purpose of this class would be great!

class NgModelOptions {
int _debounceDefaultValue = 0;
int _debounceBlurValue;
int _debounceChangeValue;
int _debounceInputValue;

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

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";

NgModelOptions(NodeAttrs attrs) {
var jsonFormattedOptions = attrs["ng-model-options"].replaceFirst("debounce", "'debounce'")
.replaceAll("'", "\"");
Map options = convert.JSON.decode(jsonFormattedOptions);

if(options["debounce"] is int){
_debounceDefaultValue = options["debounce"];
}else{
if (options["debounce"].containsKey(_DEBOUNCE_DEFAULT_KEY)){
_debounceDefaultValue = options["debounce"][_DEBOUNCE_DEFAULT_KEY];
}
_debounceBlurValue = options["debounce"][_DEBOUNCE_BLUR_KEY];
_debounceChangeValue = options["debounce"][_DEBOUNCE_CHANGE_KEY];
_debounceInputValue = options["debounce"][_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){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if () {
} else {
}

notice ws & formatting

func();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add missing {}

return null;
} else {
return new async.Timer(new Duration(milliseconds: delay), func);
}
}
}
Loading