Je construis une interface utilisateur d'autorisations, j'ai une liste d'autorisations avec une liste de sélection à côté de chaque autorisation. Les autorisations sont représentées par un tableau observable d'objets liés à une liste de sélection:
<div data-bind="foreach: permissions">
<div class="permission_row">
<span data-bind="text: name"></span>
<select data-bind="value: level, event:{ change: $parent.permissionChanged}">
<option value="0"></option>
<option value="1">R</option>
<option value="2">RW</option>
</select>
</div>
</div>
Maintenant, le problème est le suivant: l'événement change est déclenché lorsque l'interface utilisateur remplit pour la première fois. J'appelle ma fonction ajax, obtient la liste des autorisations, puis l'événement est déclenché pour chacun des éléments d'autorisation. Ce n'est vraiment pas le comportement que je veux. Je veux qu'il soit soulevé uniquement lorsqu'un utilisateur choisit réellement une nouvelle valeur pour l'autorisation dans la liste de sélection , comment puis-je le faire?
En fait vous voulez savoir si l'événement est déclenché par l'utilisateur ou le programme , et il est évident que cet événement se déclenchera pendant l'initialisation.
L’approche knock-out consistant à ajouter subscription
n’aidera pas dans tous les cas, car, dans la plupart des cas, le modèle sera implémenté comme ceci
(actual KO initilization)
(logical init like load JSON , get data etc)
L’étape réelle que nous voulons capturer est celle des modifications dans 3, mais à la deuxième étape, subscription
sera appelé, Un meilleur moyen consiste donc à ajouter au changement d’événement de
<select data-bind="value: level, event:{ change: $parent.permissionChanged}">
et a détecté l'événement dans la fonction permissionChanged
this.permissionChanged = function (obj, event) {
if (event.originalEvent) { //user changed
} else { // program changed
}
}
Ceci est juste une hypothèse, mais je pense que cela se produit parce que level
est un nombre. Dans ce cas, la liaison value
déclenchera un événement change
pour mettre à jour level
avec la valeur de chaîne. Vous pouvez donc résoudre ce problème en vous assurant que level
est une chaîne de départ.
De plus, la méthode la plus simple consiste à ne pas utiliser de gestionnaires d'événements, mais à utiliser des observables et des abonnements. Faites de level
un observable, puis ajoutez-y un abonnement, qui sera exécuté à chaque changement de level
.
Voici une solution qui peut aider avec ce comportement étrange. Je ne pouvais pas trouver de meilleure solution que de placer un bouton pour déclencher manuellement l'événement de modification.
EDIT: Peut-être qu'une reliure personnalisée comme celle-ci pourrait aider:
ko.bindingHandlers.changeSelectValue = {
init: function(element,valueAccessor){
$(element).change(function(){
var value = $(element).val();
if($(element).is(":focus")){
//Do whatever you want with the new value
}
});
}
};
Et dans votre attribut select data-bind, ajoutez:
changeSelectValue: yourSelectValue
J'utilise cette liaison personnalisée (basée sur this fiddle de RP Niemeyer, voir sa réponse à this question), qui permet de s'assurer que la valeur numérique est correctement convertie d'une chaîne à l'autre Michael Best):
Javascript:
ko.bindingHandlers.valueAsNumber = {
init: function (element, valueAccessor, allBindingsAccessor) {
var observable = valueAccessor(),
interceptor = ko.computed({
read: function () {
var val = ko.utils.unwrapObservable(observable);
return (observable() ? observable().toString() : observable());
},
write: function (newValue) {
observable(newValue ? parseInt(newValue, 10) : newValue);
},
owner: this
});
ko.applyBindingsToNode(element, { value: interceptor });
}
};
Exemple HTML:
<select data-bind="valueAsNumber: level, event:{ change: $parent.permissionChanged }">
<option value="0"></option>
<option value="1">R</option>
<option value="2">RW</option>
</select>
Si vous utilisez une valeur observable au lieu d'une valeur primitive, la sélection ne déclenchera pas d'événements de modification lors de la liaison initiale. Vous pouvez continuer à vous lier à l'événement change plutôt que de vous abonner directement à l'observable.
Rapide et sale, utilisant un simple drapeau:
var bindingsApplied = false;
var ViewModel = function() {
// ...
this.permissionChanged = function() {
// ignore, if flag not set
if (!flag) return;
// ...
};
};
ko.applyBindings(new ViewModel());
bindingsApplied = true; // done with the initial population, set flag to true
Si cela ne fonctionne pas, essayez d'encapsuler la dernière ligne dans setTimeout () - les événements sont asynchrones. Par conséquent, la dernière est peut-être toujours en attente lorsque applyBindings () est déjà renvoyé.
Si vous travaillez avec Knockout, utilisez les fonctionnalités clés de Knockout.
Utilisez la méthode ko.computed()
et faites et déclenchez un appel ajax dans cette fonction.
J'ai eu un problème similaire et je viens de modifier le gestionnaire d'événements pour vérifier le type de la variable. Le type est défini uniquement après que l'utilisateur a sélectionné une valeur, et non lors du premier chargement de la page.
self.permissionChanged = function (l) {
if (typeof l != 'undefined') {
...
}
}
Cela semble fonctionner pour moi.
utilisez ceci:
this.permissionChanged = function (obj, event) {
if (event.type != "load") {
}
}