Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow null values ​​in a select and also allow customization of the comparison function #77

Open
insinfo opened this issue Sep 10, 2024 · 0 comments
Labels
bug Something isn't working

Comments

@insinfo
Copy link

insinfo commented Sep 10, 2024

@GZGavinZhao

Description

allow null values ​​in a select and also allow customization of the comparison function

I made a custom CustomSelectControlValueAccessor to allow this, I think this can be integrated into AngularDart just like it was integrated into Angular typscript

angular/angular#10349
https://stackoverflow.com/questions/47477167/use-comparewith-function-from-angular-material-mat-select-component-with-linked

// ignore_for_file: unnecessary_import, implementation_imports
import 'dart:html';
import 'package:ngdart/angular.dart';
import 'package:ngforms/ngforms.dart';
import 'package:ngforms/src/directives/control_value_accessor.dart'
    show ChangeHandler, ControlValueAccessor, ngValueAccessor, TouchHandler;

bool isPrimitive(val) {
  return val is num || val is bool || val == null || val is String;
}

/// function for compare
bool _equals(Object? a, Object? b) {
  return a == b;
}

String _buildValueString(String? id, Object? value) {
  if (id == null) return '$value';
  if (!isPrimitive(value)) value = 'Object';
  var s = '$id: $value';
  // TODO: Fix this magic maximum 50 characters (from TS-transpile).
  if (s.length > 50) {
    s = s.substring(0, 50);
  }
  return s;
}

String _extractId(String valueString) => valueString.split(':')[0];

const selectValueAccessorCustom = ExistingProvider.forToken(
  ngValueAccessor,
  CustomSelectControlValueAccessor,
);

/// The accessor for writing a value and listening to changes on a select
/// element.
///
/// Note: We have to listen to the 'change' event because 'input' events aren't
/// fired for selects in Firefox and IE:
/// https://bugzilla.mozilla.org/show_bug.cgi?id=1024350
/// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4660045
@Directive(
  selector: 'select[ngControl],select[ngFormControl],select[ngModel]',
  providers: [selectValueAccessorCustom],
  // SelectControlValueAccessor must be visible to NgSelectOption.
  visibility: Visibility.all,
)
class CustomSelectControlValueAccessor extends Object
    with TouchHandler, ChangeHandler<dynamic>
    implements ControlValueAccessor<Object?> {
  final SelectElement _element;
  Object? value;
  final Map<String, Object?> _optionMap = <String, Object?>{};
  num _idCounter = 0;

  CustomSelectControlValueAccessor(HtmlElement element)
      : _element = element as SelectElement;

  @HostListener('change', ['\$event.target.value'])
  void handleChange(String value) {
    onChange(_getOptionValue(value), rawValue: value);
  }

  @override
  void writeValue(Object? value) {
    this.value = value;
    var valueString = _buildValueString(_getOptionId(value), value);

    _element.value = valueString;
  }

  @override
  void onDisabledChanged(bool isDisabled) {
    _element.disabled = isDisabled;
  }

  String _registerOption() => (_idCounter++).toString();

  /// set custom function to Check whether two object references are to the same object.
  @Input()
  set compareWith(bool Function(Object? o1, Object? o2) fn) {
    _compareWith = fn;
  }

  /// use equals (a == b) function for compare
  @Input()
  set useEquals(bool val) {
    _compareWith = _equals;
  }

  /// use identical or custom function for compare
  bool Function(Object? o1, Object? o2) _compareWith = identical;

  String? _getOptionId(Object? value) {
    for (var id in _optionMap.keys) {
      //if (identical(_optionMap[id], value)) return id;
      if (_compareWith(_optionMap[id], value)) return id;
    }
    return null;
  }

  /// allow null values ​​in a select option
  ///  <option value="null" selected>Selecione</option>
  ///  <option ngValue="null" selected>Selecione</option>
  @Input()
  bool enableNullValue = false;

  dynamic _getOptionValue(String valueString) {
    final ngVal = _optionMap[_extractId(valueString)];

    if (enableNullValue) {
      return ngVal;
    }
    return ngVal ?? valueString;
  }
}

/// Marks <option> as dynamic, so Angular can be notified when options change.
///
/// ### Example
///
///     <select ngControl="city">
///       <option *ngFor="let c of cities" [value]="c"></option>
///     </select>
@Directive(
  selector: 'option',
)
class CustomNgSelectOption implements OnDestroy {
  final OptionElement _element;
  final CustomSelectControlValueAccessor? _select;
  late final String id;

  CustomNgSelectOption(HtmlElement element, @Optional() @Host() this._select)
      : _element = element as OptionElement {
    if (_select != null) id = _select!._registerOption();
  }

  @Input('ngValue')
  set ngValue(Object? value) {
    var select = _select;
    if (select == null) return;

    if (select.enableNullValue) {
      select._optionMap[id] = value == 'null' ? null : value;
    } else {
      select._optionMap[id] = value;
    }

    _setElementValue(_buildValueString(id, value));
    select.writeValue(select.value);
  }

  @Input('value')
  set value(Object? value) {
    var select = _select;
    _setElementValue(value as String);
    if (select != null) select.writeValue(select.value);
  }

  void _setElementValue(String value) {
    _element.value = value;
  }

  @override
  void ngOnDestroy() {
    var select = _select;
    if (select != null) {
      select._optionMap.remove(id);
      select.writeValue(select.value);
    }
  }
}

Please provide the steps to reproduce the bug

<div class="form-group col-md-2 mb-3">
            <label class="form-label">Estado Civil:</label>

            <select
              class="form-select"
              [(ngModel)]="matricula.candidato.estadoCivil"
              ngControl="estadoCivil"
              [enableNullValue]="true"
            >
              <option value="null" selected>Selecione</option>
              <option
                *ngFor="let estadoCivil of estadoCivilList "
                [ngValue]="estadoCivil"
              >
                {{estadoCivil}}
              </option>
            </select>
          </div>

Please provide the exception or error you saw

No response

Please provide the dependency environment you discovered this bug in (run dart pub deps -s compact)

Dart SDK version: 3.2.1 (stable) (Wed Nov 22 08:59:13 2023 +0000) on "windows_x64"

Anything else?

No response

@insinfo insinfo added the bug Something isn't working label Sep 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant