Zmień wydarzenie na wybierz z wiązaniem knockout, skąd mam wiedzieć, czy to prawdziwa zmiana

87

Buduję interfejs użytkownika dotyczący uprawnień, mam listę uprawnień z listą wyboru obok każdego uprawnienia. Uprawnienia są reprezentowane przez obserwowalną tablicę obiektów, które są powiązane z listą wyboru:

<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>

Teraz problem jest następujący: zdarzenie zmiany jest zgłaszane, gdy interfejs użytkownika jest wypełniany po raz pierwszy. Wywołuję moją funkcję Ajax, pobieram listę uprawnień, a następnie zdarzenie jest zgłaszane dla każdego elementu uprawnień. To naprawdę nie jest zachowanie, którego chcę. Chcę, aby było podnoszone tylko wtedy, gdy użytkownik naprawdę wybierze nową wartość uprawnienia na liście wyboru , jak mogę to zrobić?

facet schaller
źródło

Odpowiedzi:

107

Właściwie chcesz dowiedzieć się, czy zdarzenie jest wyzwalane przez użytkownika lub program i jest oczywiste, że zdarzenie zostanie wywołane podczas inicjalizacji.

Podejście polegające na dodawaniu metodą knockout subscriptionnie pomoże we wszystkich przypadkach, ponieważ w większości modelu zostanie zaimplementowany w ten sposób

  1. zainicjuj model z niezdefiniowanymi danymi, tylko strukturą (actual KO initilization)
  2. zaktualizuj model danymi początkowymi (logical init like load JSON , get data etc)
  3. Interakcja i aktualizacje użytkownika

Rzeczywisty krok, który chcemy uchwycić, to zmiany w 3, ale w drugim kroku subscriptionotrzymamy połączenie, więc lepszym sposobem jest dodanie do zdarzenia zmiany, takiej jak

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

i wykrył zdarzenie w permissionChangeddziałaniu

this.permissionChanged = function (obj, event) {

  if (event.originalEvent) { //user changed

  } else { // program changed

  }

}
Sarath
źródło
5
Myślę, że to powinno być oznaczone jako poprawna odpowiedź. Miałem dokładnie taki scenariusz, jak opisano w pytaniu, i przetestowałem przekazywanie ciągów znaków jako klucz do selectelementu. Jednak nawet gdy początkowa wartość zaznaczenia pasowała do jednej z opcji, nadal wyzwalała changezdarzenie. Kiedy zaimplementowałem ten prosty ifblok w module obsługi, wszystko działało zgodnie z oczekiwaniami. Najpierw wypróbuj tę metodę. Dzięki, @Sarath!
Pflugs
Podoba mi się koncepcja rozróżnienia między zmianą użytkownika a zmianą programu. Jest to pomocne w codziennych przypadkach, zwłaszcza w przypadku nokautu, w którym obserwowalne najprawdopodobniej zmienią wartość bez interakcji użytkownika.
rodiwa
Zgadzam się z @Pflugs, rozróżniając typy zdarzeń, które zadziałały dla mnie i to powinna być akceptowana odpowiedź.
Emma Middlebrook,
1
def powinna być zaakceptowaną odpowiedzią ... właściwość typu zdarzenia może być użyta do rozróżnienia wyzwalania przesłanki
Zmieniony
1
Użyto również tej metody do wykrywania, czy użytkownik zmienia wybór, a nie inny wyzwalacz. W moim przypadku aktualizacja wartości opcji za pośrednictwem powiązania „opcje” wywołała zdarzenie „zmiana”.
nath
32

To tylko przypuszczenie, ale myślę, że dzieje się tak, ponieważ leveljest liczbą. W takim przypadku valuepowiązanie wyzwoli changezdarzenie do aktualizacji levelo wartość ciągu. Dlatego możesz to naprawić, upewniając się, że leveljest to ciąg znaków, od którego należy zacząć.

Dodatkowo, bardziej "Knockout" sposobem na to jest nie używanie programów obsługi zdarzeń, ale użycie obserwabli i subskrypcji. Utwórz levelobserwowalny, a następnie dodaj do niego subskrypcję, która będzie uruchamiana po każdej levelzmianie.

Michael Best
źródło
po 2 godzinach szukania przyczyny, że mój formularz wyzwalał zdarzenie „zmiana” po załadowaniu strony, zgadujesz, że mnie uratował.
Samih A
Twoja odpowiedź dała mi pewien pomysł ... Celowo zmieniłem typ pochodzący z adresu URL, więc moja funkcja subskrypcji uruchamia się przy ładowaniu strony (chciałem, aby przetwarzała zmienną adresu URL, a nie tylko wtedy, gdy moje menu wywoływało zmianę). Czy jest na to mniej hakerski sposób?
Jiffy
4

Oto rozwiązanie, które może pomóc w tym dziwnym zachowaniu. Nie mogłem znaleźć lepszego rozwiązania niż umieszczenie przycisku, aby ręcznie wywołać zdarzenie zmiany.

EDYCJA: Może takie niestandardowe wiązanie mogłoby pomóc:

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
            }

        });

    }
  };

W wybranym atrybucie powiązania danych dodaj:

changeSelectValue: yourSelectValue
Ingro
źródło
oh ... znaczy jak przycisk Zapisz .. nie jest dobre dla mnie .. I naprawdę wystarczy to do dzieła :)?
facet Schaller
Edytuj odpowiedź za pomocą lepszego rozwiązania (miejmy nadzieję).
Ingro
Hej Ingro, to zostało oflagowane (niepoprawnie) jako nie odpowiedź. Przeformułowałem Twoje początkowe zdanie, aby osoby zgłaszające treści nie pomyślały przypadkowo, że publikujesz pytanie jako odpowiedź. Mam nadzieję że to pomoże! :)
jmort253
@ jmort253: przepraszam, to była moja wina. Lepiej nie odpowiadać, używając „Ja też mam ten sam problem”, ponieważ wygląda na to, że pytasz o coś innego. Zamiast tego podaj bezpośrednio rozwiązanie odpowiedzi i wyjaśnij, jak to działa.
Qantas 94 Heavy
1
Przepraszam, mój błąd. To była jedna z pierwszych odpowiedzi, które opublikowałem na stackoverflow i nie znałem wszystkich zasad. Dzięki za edycję!
Ingro
3

Używam tego niestandardowego powiązania (na podstawie tego skrzypiec RP Niemeyera, zobacz jego odpowiedź na to pytanie), które zapewnia, że ​​wartość liczbowa jest poprawnie konwertowana ze ciągu na liczbę (zgodnie z rozwiązaniem Michaela Besta):

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 });
    }
};

Przykładowy kod 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
źródło
2

Jeśli użyjesz obserwowalnej zamiast wartości pierwotnej, wybór nie zgłosi zdarzeń zmiany przy początkowym powiązaniu. Możesz kontynuować tworzenie powiązania ze zdarzeniem zmiany, zamiast subskrybować bezpośrednio to, co można zaobserwować.

Stefan Smith
źródło
1

Szybko i brudno, z wykorzystaniem prostej flagi:

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

Jeśli to nie zadziała, spróbuj zawinąć ostatnią linię w setTimeout () - zdarzenia są asynchroniczne, więc być może ostatni z nich jest nadal w toku, gdy applyBindings () jest już zwrócony.

Niko
źródło
cześć dzięki za odpowiedź, to nie zadziała, ponieważ dzwonię do ko.applyBindings, gdy dom jest gotowy, ale moje uprawnienia a są wypełniane w moim modelu na późniejszym etapie ... więc flaga byłaby prawdziwa, ale problem nadal by się pojawił.
guy schaller
0

Miałem podobny problem i właśnie zmodyfikowałem procedurę obsługi zdarzeń, aby sprawdzić typ zmiennej. Typ jest ustawiany dopiero po wybraniu wartości przez użytkownika, a nie przy pierwszym załadowaniu strony.

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

To wydaje się działać dla mnie.

fireydude
źródło
0

Użyj tego:

this.permissionChanged = function (obj, event) {

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

    } 
}
AGuyCalledGerald
źródło
0

Spróbuj tego:

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

event.type == "change"
event.type == "load" 
Chanaka Sampath
źródło
Drugim parametrem nie był indeks, dla mnie było to zdarzenie.
sairfan
@sairfan dodaj kilka parametrów do funkcji i sprawdź, z którego parametru otrzymujesz zdarzenie za pomocą debugera
Chanaka Sampath
-1

Jeśli pracujesz z knockoutem, użyj kluczowej funkcjonalności, jaką jest nokaut obserwowalnej funkcjonalności.
Użyj ko.computed()metody i wykonaj i uruchom wywołanie ajax w ramach tej funkcji.

vasu
źródło