2015-07-10 5 views
14

Ich versuche, mit unterschiedlichen Verhalten von ngModel in verschiedenen Browsern umzugehen.ngModel - Wie man mit seinem unterschiedlichen Verhalten in verschiedenen Browsern umgehen kann?

Meine Anweisung Wrapps jqueryUI Autocomplete und auf seinem select Ereignis ruft es ngModel.$setViewValue(selectedItem.id). Bei der automatischen Vervollständigung kann der Benutzer das Element per Mausklick oder durch Drücken der Taste enter auf der Tastatur auswählen.

Wenn genannte Artikel ist:

{ 
    "name": "Apple", 
    "id": "1000" 
} 

Ich erwarte, dass es nach der Wahl, die ngModel Wert wird der ausgewählte Artikel id-1000.

  • In Chrome funktioniert es OK - es setzt $viewValue und $modelValue richtig ($modelValue=1000).

  • In Firefox gibt es Modell wie in Chrome ($modelValue=1000), aber wenn ich woanders klicken - Unschärfe macht (dann Browser feuert wahrscheinlich change Ereignis), Modelländerungen und es wird gleich wie sichtbarer Eingabewert ($modelValue='Apple').

  • In IE 11 setzt das Modell nur dann korrekt, wenn ich das Element mit einem Mausklick auswähle. Wenn ich es durch Drücken enter auswählen, wird das Modell sichtbar Eingangswert ($modelValue='Apple')

Hier ist plunkr: http://plnkr.co/edit/o2Jkgprf8EakGqnpu22Y?p=preview

Ich möchte das gleiche Verhalten in jedem Browser erreichen. Wie geht man mit diesen Problemen um?

+0

Ich denke, Sie sind viel besser, zwei verschiedene Modelle zu verwenden, eine, die den aufgelösten Async-Wert enthält (wahrscheinlich privat), und eine, die der Benutzer verwendet, um die Abfrage (öffentlich) einzugeben. So wie es steht, wenn man einen vollständigen falschen Namen eingibt (einen, der nicht in der Liste der Früchte ist), dann ist dies der Modellwert. Was wahrscheinlich nicht wirklich hilfreich ist, wenn Sie erwarten, dass es eine ID ist. – Yoshi

+0

@Yoshi, Das, was Sie erwähnt haben, ist nur eine Validierungsverbesserung - Benutzer wird nicht in der Lage sein, etwas anderes als ID einzugeben, aber es löst das Problem nicht. (Oder ich weiß nicht, wie ich es richtig umsetzen soll). Mit Ihren Hinweisen wäre es in etwa so: http://plnkr.co/edit/7nCAEhIXX2wGNR18eRqk. '$ modelValue = null 'für falsche Namen, aber in Firefox Parser-Funktion wird immer noch wieder auf Unschärfe ausgeführt, so verliere ich meinen ausgewählten Wert. – akn

+0

Wenn es nur die Ereignisse sind, können Sie versuchen, ['ngModelOptions.updateOn'] (https://docs.angularjs.org/api/ng/directive/ngModelOptions) zu verwenden. – Yoshi

Antwort

1

Ok, ich glaube, ich habe es geschafft. Die Lösung basiert auf Yoshi's comment und verwendet lokales Modell, um ausgewählte Daten zu behalten.

Wenn der Benutzer etwas auswählt, wird das lokale Modell auf Object gesetzt und $viewValue wird auf den Textwert des ausgewählten Elements gesetzt. Dann setzt der Parser die id Eigenschaft des lokalen Modells als $modelValue.

select: function(event, ui) { 
    if(ui.item && ui.item.data){ 
    model = ui.item.data 
    ngModel.$setViewValue(model.name); 
    scope.$apply(); 
    } 
} 

ngModel.$parsers.push(function(value) { 
    if(_.isObject(model) && value!==model.name){ 
    model = value; 
    return model; 
    } 
    return model.id; 
}); 

Parser-Funktion auch eine wichtige Sache. Weil es ausgeführt wird, wenn Benutzer etwas oder auf das Ereignis change eingeben (das war das Problem in Firefox!), Überprüft es, ob der Wert dem aktuellen Wert des aktuellen lokalen Modells entspricht, und wenn nicht, ändert es das lokale Modell in diesen Wert. Es bedeutet, dass, wenn die Parserfunktion von 10 ausgeführt wird, der Ereigniswert dem Textwert entspricht, also $modelValue nicht geändert wird, aber wenn der Benutzer type model auf den typisierten Wert aktualisiert (wird String).

Die Validator-Funktion prüft, ob das lokale Modell ein Object ist. Wenn nicht, bedeutet dies, dass das Feld ungültig ist, so dass $modelValue standardmäßig nicht mehr angezeigt wird. Hier

ist die plunkr: http://plnkr.co/edit/2ZkXFvgLIwDljfJoyeJ1?p=preview

(In-Formatierungsfunktion kehre ich das, was kommt, so $viewValue vorübergehend ein Object aber dann in $render Methode, die ich $setViewValue nennen $viewValue und $modelValue richtig einzustellen, so dass es String wird . Ich hörte $setViewValue sollte nicht in $render Methode ausgeführt werden, aber ich sehe keinen anderen Weg, um richtig zu setzen $modelValue wenn etwas von draußen kommt).

2

Dies scheint http://bugs.jqueryui.com/ticket/8878

bezogen werden, wie in dem obigen Link hingewiesen wird das Änderungsereignis nur in Firefox ausgelöst und nicht in Chrome. In diesem Fall wird $ setViewValue erneut ausgelöst, wenn außerhalb geklickt wird und der Modellwert auf "Apple" gesetzt ist.

Es gibt Änderung Rückruf für Autocomplete jquery Ui Widget. Um beide Fälle/Browser zu behandeln, müssten Sie den Anzeigewert bei diesem Aufruf erneut explizit setzen (und es funktioniert).

http://plnkr.co/edit/GFxhzwieBJTSL8zjSPSZ?p=preview

link: function(scope, elem, attrs, ngModel) { 
     elem.on('change', function(){ 
     // This will not be printed in Chrome and only on firefox 
     console.log('change'); 
    }); 


    select: function(event, ui) { 
     ngModel.$setViewValue(ui.item.data.id); 
     scope.$apply(); 
    }, 
    // To handle firefox browser were change event is triggered 
    // when clicked outside/blur 
    change: function(event, ui) { 
     ngModel.$setViewValue(ui.item.data.id); 
     scope.$apply(); 
    } 
+0

Danke, es ist keine perfekte Lösung, aber es kann helfen. Außerdem löst es nicht das Problem mit der'Eingabe'-Taste in IE. – akn

0

hatte ich similiar Kämpfe mit der ngModelController und $setViewValue.

Schließlich suchte ich nach alternativen Lösungen. Ein Ansatz, den ich fand, der ziemlich gut funktionierte, war, ein neues Element als Komponentendirektive zu erstellen, das das Eingabe-Tag als ein übergangenes Element enthält.

app.directive('fruitAutocomplete', function($http) { 
    return { 
    restrict: 'E', 
    require: 'ngModel', 
    transclude: true, 
    template: '<ng-transclude></ng-transclude>', 
    link: function(scope, elem, attrs, ngModelController) { 
     var $input = elem.find('input'); 

     $input.autocomplete({ 
     ... 
     }); 
    } 
    } 
}) 

Im HTML:

<fruit-autocomplete name="fruit" ng-model="model.fruit"> 
    <input ng-disabled="inputDisabled" placeholder="input fruit"/> 
</fruit-autocomplete> 

hier ein arbeitet Plunker

Mit dieser vorgeschlagenen Lösung können Sie die ngModelController und das modale Zusammenspiel seiner eigenen benutzerdefinierten Element jQueryUI isolieren und es nicht interferieren mit dem "normalen" <input>-Tag und Sie sind nicht von dem jQueryUI-Fehler betroffen.

Durch das <input> Tag als transkludiert Element verwenden Sie noch von den meisten der Winkeleingabe Goodies wie ng-disabled profitieren können, placeholder, etc ...

+0

In dieser Lösung verlieren Sie viel Standardfunktionalität. Z.B. Sie können 'Platzhalter 'nicht setzen, Sie können' ng-change' oder 'ng-disabled' nicht verwenden. Es kann in Ordnung sein, wenn Sie Ihre Anweisung für wirklich spezifische Fälle verwenden möchten, aber nicht, wenn Sie wiederverwendbare Anweisungen erstellen, die in großen Anwendungen verwendet werden;) – akn

+0

Herausforderung angenommen! Bitte berücksichtigen Sie meine aktualisierte Antwort mit der Verwendung von Transclusion. – DanEEStar