changer l'événement lors de la sélection avec une liaison knockout, comment puis-je savoir si c'est un vrai changement

87

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 permissions sont représentées par un tableau d'objets observables qui sont 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 de modification est déclenché lorsque l'interface utilisateur se remplit pour la première fois. J'appelle ma fonction ajax, j'obtiens 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 souhaite. Je souhaite qu'il soit déclenché uniquement lorsqu'un utilisateur sélectionne vraiment une nouvelle valeur pour l'autorisation dans la liste de sélection , comment puis-je faire cela?

guy schaller
la source

Réponses:

107

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 lors de l'initialisation.

L'approche knock-out de l'ajout subscriptionn'aidera pas dans tous les cas, pourquoi parce que dans la plupart des modèles seront implémentés comme ceci

  1. initier le modèle avec des données non définies, structure juste (actual KO initilization)
  2. mettre à jour le modèle avec les données initiales (logical init like load JSON , get data etc)
  3. Interaction des utilisateurs et mises à jour

L'étape réelle que nous voulons capturer est les changements dans 3, mais dans la deuxième étape, subscriptionnous recevrons un appel, donc une meilleure façon est d'ajouter un changement d'événement comme

 <select data-bind="value: level, event:{ change: $parent.permissionChanged}">

et détecté l'événement en permissionChangedfonction

this.permissionChanged = function (obj, event) {

  if (event.originalEvent) { //user changed

  } else { // program changed

  }

}
Sarath
la source
5
Je pense que cela devrait être marqué comme la bonne réponse. J'ai eu le scénario exact décrit dans la question, et j'ai testé des chaînes de transmission comme la clé de l' selectélément. Cependant, même lorsque la valeur initiale de la sélection correspond à l'une des options, cela déclenche toujours l' changeévénement. Lorsque j'ai implémenté ce simple ifbloc dans le gestionnaire, tout fonctionnait comme prévu. Essayez d'abord cette méthode. Merci, @Sarath!
Pflugs
J'aime le concept de différenciation entre l'utilisateur changé et le programme changé. Ceci est utile dans les cas quotidiens, en particulier avec KO où les observables sont plus susceptibles de changer de valeur sans aucune interaction de l'utilisateur.
rodiwa
D'accord avec @Pflugs, différencier les types d'événements a fonctionné pour moi et cela devrait être la réponse acceptée.
Emma Middlebrook
1
def devrait être une réponse acceptée ... la propriété de type d'événement pourrait être utilisée pour différencier le déclenchement de la premissionChanged
caniaskyouaquestion
1
Également utilisé cette méthode pour détecter si un utilisateur modifiait une sélection plutôt qu'un autre déclencheur. Dans mon cas, la mise à jour des valeurs d'options via la liaison "options" a déclenché l'événement "change".
nath
32

Ce n'est qu'une supposition, mais je pense que cela se produit parce que levelc'est un nombre. Dans ce cas, la valueliaison déclenchera un changeévénement à mettre à jour levelavec la valeur de chaîne. Vous pouvez donc résoudre ce problème en vous assurant qu'il levels'agit d'une chaîne pour commencer.

De plus, la manière la plus "Knockout" de faire ceci est de ne pas utiliser de gestionnaires d'événements, mais d'utiliser des observables et des abonnements. Créez levelun observable, puis ajoutez-y un abonnement, qui sera exécuté à chaque levelmodification.

Michael Best
la source
après 2 heures de fouille de la cause de mon formulaire déclenchant l'événement 'change' après le chargement de la page, vous devinez m'a sauvé.
Samih A
Votre réponse m'a donné une idée ... J'ai délibérément changé le type provenant de l'url, donc ma fonction d'abonnement se déclencherait au chargement de la page (je voulais qu'elle traite une var d'url, pas seulement lorsque ma liste déroulante déclenchait un changement). Y a-t-il une manière moins pirate de faire cela?
Jiffy
4

Voici une solution qui peut aider avec ce comportement étrange. Je n'ai pas pu trouver de meilleure solution que de placer un bouton pour déclencher manuellement l'événement de modification.

EDIT: Peut-être qu'une liaison 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 de liaison de données, ajoutez:

changeSelectValue: yourSelectValue
Ingro
la source
oh ... tu veux dire comme un bouton de sauvegarde? .. pas bon pour moi .. j'ai vraiment juste besoin de ça pour fonctionner :)
guy schaller
Modifiez la réponse avec une meilleure solution (espérons-le).
Ingro
Hey Ingro, cela a été signalé (à tort) comme n'étant pas une réponse. J'ai reformulé votre phrase initiale pour que les signaleurs ne pensent pas accidentellement que vous postez une question en tant que réponse. J'espère que cela t'aides! :)
jmort253
@ jmort253: désolé, c'était ma faute. Il vaut mieux ne pas répondre en utilisant "J'ai le même problème aussi", car cela semble donner l'impression que vous demandez autre chose. Au lieu de cela, fournissez directement une solution à une réponse et expliquez comment cela fonctionne.
Qantas 94 Heavy
1
Ouais désolé, mon erreur. C'était l'une des premières réponses que j'ai postées sur stackoverflow et je ne connaissais pas toutes les règles. Merci pour la modification!
Ingro
3

J'utilise cette liaison personnalisée (basée sur ce violon de RP Niemeyer, voir sa réponse à cette question), qui s'assure que la valeur numérique est correctement convertie de chaîne en nombre (comme suggéré par la solution de 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>
mhu
la source
2

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 de modification, plutôt que de vous abonner directement à l'observable.

Stefan Smith
la source
1

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 un setTimeout () - les événements sont asynchrones, donc peut-être que le dernier est toujours en attente lorsque applyBindings () est déjà retourné.

Niko
la source
salut merci pour la réponse, cela ne fonctionnera pas pour moi car j'appelle ko.applyBindings lorsque le dom est prêt, mais mes autorisations sont remplies sur mon modèle à un stade ultérieur ... donc le drapeau serait vrai mais le problème se produirait toujours.
guy schaller
0

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 n'est défini qu'une fois que l'utilisateur a sélectionné une valeur, pas lors du premier chargement de la page.

self.permissionChanged = function (l) {
    if (typeof l != 'undefined') {
        ...
    }
}

Cela semble fonctionner pour moi.

Fireydude
la source
0

utilisez ceci:

this.permissionChanged = function (obj, event) {

    if (event.type != "load") {

    } 
}
AGuyCalledGerald
la source
0

Essaye celui-là:

self.GetHierarchyNodeList = function (data, index, event)
{
    debugger;
    if (event.type != "change") {
        return;
    }        
} 

event.type == "change"
event.type == "load" 
Chanaka Sampath
la source
Le 2ème paramètre n'était pas un index, pour moi c'était un événement.
sairfan le
@sairfan ajoute quelques paramètres à la fonction et trouve à partir de quel paramètre vous obtenez l'événement à l'aide du débogueur
Chanaka Sampath
-1

Si vous utilisez Knockout, utilisez la fonctionnalité clé de Knockout de fonctionnalité observable.
Utilisez la ko.computed()méthode et faites et déclenchez un appel ajax dans cette fonction.

Vasu
la source