@@ -439,6 +439,192 @@ class InputNumberLike {
439
439
}
440
440
}
441
441
442
+ /**
443
+ * This directive affects which IDL attribute will be used to read the value of
444
+ * date/time related input directives. Recognized values for this directive are:
445
+ *
446
+ * - [DATE] : [dom.InputElement] .valueAsDate will be read.
447
+ * - [NUMBER] : [dom.InputElement] .valueAsNumber will be read.
448
+ * - [STRING] : [dom.InputElement] .value will be read.
449
+ *
450
+ * The default is [DATE] . Use other settings, e.g., when an app needs to support
451
+ * browsers that treat date-like inputs as text (in such a case the [STRING]
452
+ * kind would be appropriate) or, for browsers that fail to conform to the
453
+ * HTML5 standard in their processing of date-like inputs.
454
+ */
455
+ @NgDirective (selector: 'input[type=date][ng-model][ng-bind-type]' )
456
+ @NgDirective (selector: 'input[type=time][ng-model][ng-bind-type]' )
457
+ @NgDirective (selector: 'input[type=datetime][ng-model][ng-bind-type]' )
458
+ @NgDirective (selector: 'input[type=datetime-local][ng-model][ng-bind-type]' )
459
+ @NgDirective (selector: 'input[type=month][ng-model][ng-bind-type]' )
460
+ @NgDirective (selector: 'input[type=week][ng-model][ng-bind-type]' )
461
+ class NgBindTypeForDateLike {
462
+ static const
463
+ DATE = 'date' ,
464
+ NUMBER = 'number' ,
465
+ STRING = 'string' ,
466
+ DEFAULT = DATE ;
467
+ static const VALID_VALUES = const < String > [DATE , NUMBER , STRING ];
468
+
469
+ final dom.InputElement inputElement;
470
+ String _idlAttrKind = DEFAULT ;
471
+
472
+ NgBindTypeForDateLike (dom.Element this .inputElement);
473
+
474
+ @NgAttr ('ng-bind-type' )
475
+ void set idlAttrKind (final String _kind) {
476
+ String kind = _kind == null ? DEFAULT : _kind.toLowerCase ();
477
+ if (! VALID_VALUES .contains (kind))
478
+ throw "Unsupported ng-bind-type attribute value '$_kind '; "
479
+ "it should be one of $VALID_VALUES " ;
480
+ _idlAttrKind = kind;
481
+ }
482
+
483
+ String get idlAttrKind => _idlAttrKind;
484
+
485
+ dynamic get inputTypedValue {
486
+ switch (idlAttrKind) {
487
+ case DATE : return inputValueAsDate;
488
+ case NUMBER : return inputElement.valueAsNumber;
489
+ default : return inputElement.value;
490
+ }
491
+ }
492
+
493
+ void set inputTypedValue (dynamic inputValue) {
494
+ if (inputValue is DateTime ) {
495
+ inputValueAsDate = inputValue;
496
+ } else if (inputValue is num ) {
497
+ inputElement.valueAsNumber = inputValue;
498
+ } else {
499
+ inputElement.value = inputValue;
500
+ }
501
+ }
502
+
503
+ /// Input's `valueAsDate` normalized to UTC (per HTML5 std).
504
+ DateTime get inputValueAsDate {
505
+ DateTime dt;
506
+ // Wrap in try-catch due to
507
+ // https://code.google.com/p/dart/issues/detail?id=17625
508
+ try {
509
+ dt = inputElement.valueAsDate;
510
+ } catch (e) {
511
+ dt = null ;
512
+ }
513
+ return (dt != null && ! dt.isUtc) ? dt.toUtc () : dt;
514
+ }
515
+
516
+ /// Set input's `valueAsDate` . Argument is normalized to UTC if necessary
517
+ /// (per HTML standard).
518
+ void set inputValueAsDate (DateTime dt) {
519
+ inputElement.valueAsDate = (dt != null && ! dt.isUtc) ? dt.toUtc () : dt;
520
+ }
521
+ }
522
+
523
+ /**
524
+ * **Background: Standards and Browsers**
525
+ *
526
+ * According to the
527
+ * [HTML5 Standard] (http://www.w3.org/TR/html5/forms.html#the-input-element),
528
+ * the [dom.InputElement.valueAsDate] and [dom.InputElement.valueAsNumber] IDL
529
+ * attributes should be available for all date/time related input types,
530
+ * except for `datetime-local` which is limited to
531
+ * [dom.InputElement.valueNumber] . Of course, all input types support
532
+ * [dom.InputElement.value] which yields a [String] ;
533
+ * [dom.InputElement.valueAsDate] yields a [DateTime] and
534
+ * [dom.InputElement.valueNumber] yields a [num] .
535
+ *
536
+ * But not all browsers currently support date/time related inputs and of
537
+ * those that do, some deviate from the standard. Hence, this directive
538
+ * allows developers to control the IDL attribute that will be used
539
+ * to read the value of a date/time input. This is achieved via the subordinate
540
+ * 'ng-bind-type' directive; see [NgBindTypeForDateLike] for details.
541
+ *
542
+ * **Usage**:
543
+ *
544
+ * <input type="date|datetime|datetime-local|month|time|week"
545
+ * [ng-bind-type="date"]
546
+ * ng-model="myModel">
547
+ *
548
+ * **Model**:
549
+ *
550
+ * dynamic myModel; // one of DateTime | num | String
551
+ *
552
+ * This directive creates a two-way binding between the input and a model
553
+ * property. The subordinate 'ng-bind-type' directive determines which input
554
+ * IDL attribute is read (see [NgBindTypeForDateLike] for details) and
555
+ * hence the type of the read values. The type of the model property value
556
+ * determines which IDL attribute is written to: [DateTime] and [num] values
557
+ * are assigned to [dom.InputElement.valueAsDate] and
558
+ * [dom.InputElement.valueNumber] , respectively; [String] and `null` values
559
+ * are assigned to [dom.InputElement.value] . Setting the model to `null` will
560
+ * clear the input if it is currently valid, otherwise, invalid input is left
561
+ * untouched (so that the user has an opportunity to correct it). To clear the
562
+ * input unconditionally, set the model property to the empty string ('').
563
+ *
564
+ * **Notes**:
565
+ * - As prescribed by the HTML5 standard, [DateTime] values returned by the
566
+ * `valueAsDate` IDL attribute are meant to be in UTC.
567
+ * - As of the HTML5 Editor's Draft 29 March 2014, datetime-local is no longer
568
+ * part of the standard. Other date related input are also at risk of being
569
+ * dropped.
570
+ */
571
+
572
+ @NgDirective (selector: 'input[type=date][ng-model]' ,
573
+ module: InputDateLike .moduleFactory)
574
+ @NgDirective (selector: 'input[type=time][ng-model]' ,
575
+ module: InputDateLike .moduleFactory)
576
+ @NgDirective (selector: 'input[type=datetime][ng-model]' ,
577
+ module: InputDateLike .moduleFactory)
578
+ @NgDirective (selector: 'input[type=datetime-local][ng-model]' ,
579
+ module: InputDateLike .moduleFactory)
580
+ @NgDirective (selector: 'input[type=month][ng-model]' ,
581
+ module: InputDateLike .moduleFactory)
582
+ @NgDirective (selector: 'input[type=week][ng-model]' ,
583
+ module: InputDateLike .moduleFactory)
584
+ class InputDateLike {
585
+ static Module moduleFactory () => new Module ()..factory (NgBindTypeForDateLike ,
586
+ (Injector i) => new NgBindTypeForDateLike (i.get (dom.Element )));
587
+ final dom.InputElement inputElement;
588
+ final NgModel ngModel;
589
+ final Scope scope;
590
+ NgBindTypeForDateLike ngBindType;
591
+
592
+ InputDateLike (dom.Element this .inputElement, this .ngModel, this .scope,
593
+ this .ngBindType) {
594
+ if (inputElement.type == 'datetime-local' ) {
595
+ ngBindType.idlAttrKind = NgBindTypeForDateLike .NUMBER ;
596
+ }
597
+ ngModel.render = (value) {
598
+ scope.rootScope.domWrite (() {
599
+ if (! eqOrNaN (value, typedValue)) {
600
+ typedValue = value;
601
+ }
602
+ });
603
+ };
604
+ inputElement
605
+ ..onChange.listen (relaxFnArgs (processValue))
606
+ ..onInput.listen (relaxFnArgs (processValue))
607
+ ..onBlur.listen ((e) {
608
+ ngModel.markAsTouched ();
609
+ });
610
+ }
611
+
612
+ dynamic get typedValue => ngBindType.inputTypedValue;
613
+
614
+ void set typedValue (dynamic value) {
615
+ ngBindType.inputTypedValue = value;
616
+ }
617
+
618
+ void processValue () {
619
+ var value = typedValue;
620
+ // print("processValue: value=$value, model=${ngModel.viewValue}");
621
+ if (! eqOrNaN (value, ngModel.viewValue)) {
622
+ scope.eval (() => ngModel.viewValue = value);
623
+ }
624
+ ngModel.validate ();
625
+ }
626
+ }
627
+
442
628
class _UidCounter {
443
629
static final int CHAR_0 = "0" .codeUnitAt (0 );
444
630
static final int CHAR_9 = "9" .codeUnitAt (0 );
@@ -548,14 +734,14 @@ class NgFalseValue {
548
734
* <input type="radio" ng-model="category">
549
735
*
550
736
* This creates a two way databinding between the expression specified in
551
- * ng-model and the range input elements in the DOM. If the ng-model value is
737
+ * ng-model and the range input elements in the DOM. If the ng-model value is
552
738
* set to a value not corresponding to one of the radio elements, then none of
553
739
* the radio elements will be check. Otherwise, only the corresponding input
554
740
* element in the group is checked. Likewise, when a radio button element is
555
741
* checked, the model is updated with its value. Radio buttons that have a
556
742
* `name` attribute are left alone. Those that are missing the attribute will
557
743
* have a unique `name` assigned to them. This sequence goes `001` , `001` , ...
558
- * `009` , `00A` , `00Z` , `010` , … and so on using more than 3 characters for the
744
+ * `009` , `00A` , `00Z` , `010` , and so on using more than 3 characters for the
559
745
* name when the counter overflows.
560
746
*/
561
747
@NgDirective (
@@ -598,7 +784,7 @@ class InputRadio {
598
784
* <span contenteditable= ng-model="name">
599
785
*
600
786
* This creates a two way databinding between the expression specified in
601
- * ng-model and the html element in the DOM. If the ng-model value is
787
+ * ng-model and the html element in the DOM. If the ng-model value is
602
788
* `null` , it is treated as equivalent to the empty string for rendering
603
789
* purposes.
604
790
*/
0 commit comments