Potraktuj to pytanie jako ściśle edukacyjne. Nadal jestem zainteresowany nowymi odpowiedziami i pomysłami na wdrożenie tego
tl; dr
Jak zaimplementować dwukierunkowe wiązanie danych za pomocą JavaScript?
Powiązanie danych z DOM
Przez powiązanie danych z DOM rozumiem na przykład posiadanie obiektu JavaScript a
z właściwością b
. Następnie mając <input>
element DOM (na przykład), gdy element DOM się zmienia, a
zmienia się i odwrotnie (to znaczy dwukierunkowe wiązanie danych).
Oto schemat od AngularJS na temat tego, jak to wygląda:
Więc w zasadzie mam JavaScript podobny do:
var a = {b:3};
Następnie element wejściowy (lub inny formularz), taki jak:
<input type='text' value=''>
Chciałbym, aby wartość wejściowa była wartością a.b
(na przykład), a gdy tekst wejściowy zmienia się, również chciałbym a.b
zmienić. Kiedya.b
zmiany w JavaScript, dane wejściowe ulegają zmianie.
Pytanie
Jakie są podstawowe techniki, aby to osiągnąć w zwykłym JavaScript?
W szczególności chciałbym, aby dobra odpowiedź dotyczyła:
- Jak działałoby wiązanie dla obiektów?
- Jak może działać słuchanie zmian w formularzu?
- Czy w prosty sposób można modyfikować HTML tylko na poziomie szablonu? Chciałbym nie śledzić powiązania w samym dokumencie HTML, ale tylko w JavaScript (ze zdarzeniami DOM i JavaScript z odniesieniem do użytych elementów DOM).
Co próbowałem?
Jestem wielkim fanem Wąsów, więc spróbowałem użyć go do szablonów. Jednak natrafiłem na problemy podczas próby wykonania samego wiązania danych, ponieważ wąsy przetwarza HTML jako ciąg znaków, więc po otrzymaniu jego wyniku nie mam odniesienia do tego, gdzie znajdują się obiekty w moim viewmodelu. Jedynym obejściem, jakie mogłem wymyślić, było zmodyfikowanie samego łańcucha HTML (lub utworzonego drzewa DOM) za pomocą atrybutów. Nie mam nic przeciwko użyciu innego silnika szablonów.
Zasadniczo miałem wrażenie, że komplikowałem ten problem i istnieje proste rozwiązanie.
Uwaga: Proszę nie podawać odpowiedzi, które korzystają z bibliotek zewnętrznych, zwłaszcza tych, które zawierają tysiące linii kodu. Użyłem (i lubię!) AngularJS i KnockoutJS. Naprawdę nie chcę odpowiedzi w formie „use framework x”. Optymalnie chciałbym, aby przyszły czytelnik nie wiedział, jak korzystać z wielu frameworków, aby samodzielnie zrozumieć, w jaki sposób samodzielnie wdrożyć dwukierunkowe wiązanie danych. Nie oczekuję pełnej odpowiedzi, ale takiej, która przenosi ten pomysł.
źródło
Odpowiedzi:
Abstrakcja aktualizująca oba obiekty
Przypuszczam, że istnieją inne techniki, ale ostatecznie miałbym obiekt, który zawiera odniesienie do pokrewnego elementu DOM i zapewnia interfejs, który koordynuje aktualizacje własnych danych i powiązanego elementu.
.addEventListener()
Zapewnia bardzo przyjemny interfejs do tego. Możesz nadać mu obiekt, który implementujeeventListener
interfejs, i będzie wywoływał swoje procedury obsługi z tym obiektem jakothis
wartością.Zapewnia to automatyczny dostęp zarówno do elementu, jak i powiązanych z nim danych.
Definiowanie swojego obiektu
Dziedziczenie prototypowe to dobry sposób na wdrożenie tego, choć oczywiście nie jest to wymagane. Najpierw utwórz konstruktor, który odbierze Twój element i niektóre dane początkowe.
Tak więc tutaj konstruktor przechowuje element i dane dotyczące właściwości nowego obiektu. Wiąże również
change
zdarzenie z danymelement
. Interesujące jest to, że przekazuje nowy obiekt zamiast funkcji jako drugi argument. Ale to samo nie zadziała.Implementacja
eventListener
interfejsuAby to zadziałało, twój obiekt musi zaimplementować
eventListener
interfejs. Wszystko, co jest potrzebne do tego celu, to dać obiektowihandleEvent()
metodę.Tam właśnie przychodzi dziedzictwo.
Istnieje wiele różnych sposobów, w jakie można to ustrukturyzować, ale dla twojego przykładu koordynowania aktualizacji postanowiłem, że
change()
metoda będzie akceptować tylko wartość ihandleEvent
przekaże tę wartość zamiast obiektu zdarzenia. W ten sposóbchange()
można wywoływać również bez zdarzenia.Teraz, gdy
change
zdarzenie się wydarzy, zaktualizuje zarówno element, jak i.data
właściwość. To samo stanie się, gdy zadzwonisz.change()
w swoim programie JavaScript.Korzystanie z kodu
Teraz wystarczy utworzyć nowy obiekt i pozwolić mu wykonywać aktualizacje. Aktualizacje kodu JS pojawią się na wejściu, a zmiany zdarzeń na wejściu będą widoczne dla kodu JS.
DEMO: http://jsfiddle.net/RkTMD/
źródło
Mustache.render(template,object)
, zakładając, że chcę zsynchronizować obiekt z szablonem (niespecyficznym dla Mustache), jak mam to zrobić?input
mają zaktualizować jeden z tych punktów, nastąpi mapowanie od danych wejściowych do tego punktu. Zobaczę, czy mogę wymyślić szybki przykład.Template
konstruktor, który wykonuje parsowanie, przechowuje różneMyCtor
obiekty i zapewnia interfejs do aktualizacji każdego z nich za pomocą swojego identyfikatora. Daj mi znać, jeśli masz pytania. :) EDYCJA: ... zamiast tego użyj tego linku ... Zapomniałem, że co 10 sekund mam wykładniczy wzrost wartości wejściowej, aby zademonstrować aktualizacje JS. To ogranicza.Postanowiłem więc wrzucić własne rozwiązanie do puli. Oto działające skrzypce . Uwaga: działa to tylko w bardzo nowoczesnych przeglądarkach.
Czego używa
Ta implementacja jest bardzo nowoczesna - wymaga (bardzo) nowoczesnej przeglądarki, a użytkownicy dwóch nowych technologii:
MutationObserver
s do wykrywania zmian w dom (używane są również detektory zdarzeń)Object.observe
w celu wykrycia zmian w obiekcie i powiadomienia dom. Niebezpieczeństwo, odkąd ta odpowiedź została napisana Oo zostało omówione i odrzucone przez ECMAScript TC, rozważ polifill .Jak to działa
domAttribute:objAttribute
mapowanie - na przykładbind='textContent:name'
Rozwiązanie
Oto
dataBind
funkcja, zauważ, że to tylko 20 linii kodu i może być krótsza:Oto niektóre zastosowania:
HTML:
JavaScript:
Oto działające skrzypce . Zauważ, że to rozwiązanie jest dość ogólne. Dostępny jest podgląd obiektu i obserwacja mutacji.
źródło
obj.name
ma Settera, nie można go zaobserwować zewnętrznie, ale musi nadawać, że zmienił się z Settera - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - trochę wrzuca klucz w prace dla Oo (), jeśli chcesz bardziej złożone, współzależne zachowanie za pomocą ustawiaczy. Ponadto, gdyobj.name
nie jest konfigurowalne, redefiniowanie jego ustawiacza (z różnymi sztuczkami, aby dodać powiadomienie) jest również niedozwolone - więc generyczne z Oo () są całkowicie złomowane w tym konkretnym przypadku.Chciałbym dodać do mojego prepostera. Sugeruję nieco inne podejście, które pozwoli ci po prostu przypisać nową wartość do twojego obiektu bez użycia metody. Należy jednak zauważyć, że nie jest to obsługiwane przez szczególnie starsze przeglądarki, a IE9 nadal wymaga użycia innego interfejsu.
Najważniejsze jest to, że moje podejście nie wykorzystuje wydarzeń.
Getters and Setters
Moja propozycja korzysta ze stosunkowo młodej funkcji pobierających i ustawiających , szczególnie tylko ustawiających. Ogólnie rzecz biorąc, mutatory pozwalają nam „dostosować” sposób, w jaki pewne właściwości są przypisywane wartości i pobierane.
Jedną implementacją, której będę tutaj używać, jest metoda Object.defineProperty . Działa w FireFox, GoogleChrome i - tak myślę - IE9. Nie testowałem innych przeglądarek, ale ponieważ jest to tylko teoria ...
W każdym razie akceptuje trzy parametry. Pierwszy parametr to obiekt, dla którego chcesz zdefiniować nową właściwość, drugi to ciąg znaków przypominający nazwę nowej właściwości, a ostatni „obiekt deskryptora” dostarczający informacji o zachowaniu nowej właściwości.
Dwa szczególnie interesujące deskryptory to
get
iset
. Przykład mógłby wyglądać podobnie do następującego. Zauważ, że użycie tych dwóch zabrania używania pozostałych 4 deskryptorów.Teraz korzystanie z tego staje się nieco inne:
Chcę podkreślić, że działa to tylko w przypadku nowoczesnych przeglądarek.
Działające skrzypce: http://jsfiddle.net/Derija93/RkTMD/1/
źródło
Proxy
obiekty Harmony :) Setery wydają się fajnym pomysłem, ale czy nie wymagałoby to modyfikacji rzeczywistych obiektów? Na marginesie -Object.create
można go tutaj użyć (ponownie, zakładając, że nowoczesna przeglądarka pozwoliła na drugi parametr). Ponadto setter / getter może być użyty do „rzutowania” innej wartości na obiekt i element DOM :). Zastanawiam się, czy masz jakieś spostrzeżenia na temat tworzenia szablonów, co wydaje się tutaj prawdziwym wyzwaniem, szczególnie jeśli chodzi o ładną strukturę :)Proxy
, jak powiedziałeś;) Zrozumiałem wyzwanie polegające na zsynchronizowaniu dwóch różnych właściwości. Moja metoda eliminuje jedno z obu.Proxy
Wyeliminuje konieczność używania pobierające / ustawiające, można powiązać elementy nie wiedząc, jakie właściwości mają. Miałem na myśli to, że pobierający mogą zmienić więcej niż bindTo.value, mogą zawierać logikę (a może nawet szablon). Pytanie brzmi: jak zachować tego rodzaju dwukierunkowe wiązanie z myślą o szablonie? Powiedzmy, że mapuję mój obiekt na formularz, chciałbym zachować zarówno element, jak i formularz zsynchronizowany i zastanawiam się, jak sobie poradzę w takich sprawach. Na przykład możesz sprawdzić, jak to działa w przypadku nokautu. Learn.knockoutjs.com/#/?tutorial=introMyślę, że moja odpowiedź będzie bardziej techniczna, ale nie różna, ponieważ inni prezentują to samo przy użyciu różnych technik.
Po pierwsze, rozwiązaniem tego problemu jest użycie wzorca projektowego znanego jako „obserwator”, pozwala on oddzielić dane od prezentacji, dzięki czemu zmiana jednej rzeczy jest transmitowana do ich słuchaczy, ale w tym przypadku jest dwukierunkowy.
Dla sposobu DOM-JS
Aby powiązać dane z DOM z obiektem js, możesz dodać znaczniki w postaci
data
atrybutów (lub klas, jeśli potrzebujesz kompatybilności):W ten sposób można uzyskać do niego dostęp za pomocą js używając
querySelectorAll
(lub starego znajomegogetElementsByClassName
dla kompatybilności).Teraz możesz powiązać zdarzenie nasłuchując zmian na różne sposoby: jeden detektor na obiekt lub jeden duży detektor do kontenera / dokumentu. Powiązanie z dokumentem / kontenerem wywoła zdarzenie dla każdej wprowadzonej w nim zmiany lub jego potomka, będzie miało mniejszy ślad pamięci, ale odrodzi wywołania zdarzeń.
Kod będzie wyglądał mniej więcej tak:
Dla JS do DOM sposób
Będziesz potrzebował dwóch rzeczy: jeden metaobiektyw, który będzie zawierał odniesienia do elementu DOM, który jest powiązany z każdym obiektem / atrybutem js oraz sposób nasłuchiwania zmian w obiektach. Jest to w zasadzie taki sam sposób: musisz mieć sposób na słuchanie zmian w obiekcie, a następnie powiązanie go z węzłem DOM, ponieważ twój obiekt „nie może mieć” metadanych, będziesz potrzebować innego obiektu, który przechowuje metadane w pewien sposób że nazwa właściwości odwzorowuje właściwości obiektu metadanych. Kod będzie mniej więcej taki:
Mam nadzieję, że pomogłem.
źródło
Object.observe
ponieważ wsparcie jest na razie prezentowane tylko w chromie. caniuse.com/#feat=object-observeWczoraj zacząłem pisać własny sposób wiązania danych.
Bardzo zabawnie się z tym bawić.
Myślę, że to piękne i bardzo przydatne. Przynajmniej na moich testach z firefoxem i chromem, Edge też musi działać. Nie jestem pewien co do innych, ale jeśli obsługują proxy, myślę, że to zadziała.
https://jsfiddle.net/2ozoovne/1/
Oto kod:
Następnie, aby ustawić, wystarczy:
Na razie dodałem właśnie powiązanie wartości HTMLInputElement.
Daj mi znać, jeśli wiesz, jak to poprawić.
źródło
W tym linku „Łatwe dwukierunkowe wiązanie danych w JavaScript” dostępna jest bardzo prosta implementacja dwukierunkowego wiązania danych.
Poprzedni link wraz z pomysłami knockoutjs, backbone.js i agility.js doprowadził do tego lekkiego i szybkiego frameworka MVVM, ModelView.js
oparty na jQueryktóry dobrze gra z jQuery i którego jestem skromnym (a może nie tak skromnym) autorem.Ponowne odtwarzanie przykładowego kodu poniżej (z linku do postu na blogu ):
Przykładowy kod dla DataBinder
źródło
Zmiana wartości elementu może wywołać zdarzenie DOM . Detektorów reagujących na zdarzenia można użyć do implementacji powiązania danych w JavaScript.
Na przykład:
Oto kod i wersja demonstracyjna pokazująca, w jaki sposób elementy DOM można powiązać ze sobą lub z obiektem JavaScript.
źródło
Powiąż dowolne wejście HTML
zdefiniuj dwie funkcje:
użyj funkcji:
źródło
Oto pomysł,
Object.defineProperty
który bezpośrednio modyfikuje sposób dostępu do właściwości.Kod:
Stosowanie:
skrzypce: tutaj
źródło
Przeszedłem przez podstawowy przykład javascript przy użyciu programów obsługi zdarzeń onkeypress i onchange, aby utworzyć widok wiązania do naszych plików js i js do wyświetlenia
Oto przykład plunker http://plnkr.co/edit/7hSOIFRTvqLAvdZT4Bcc?p=preview
źródło
źródło
Prostym sposobem powiązania zmiennej z wejściem (wiązanie dwukierunkowe) jest po prostu bezpośredni dostęp do elementu wejściowego w module pobierającym i ustawiającym:
W HTML:
I użyć:
Bardziej wymyślny sposób na wykonanie powyższej czynności bez gettera / setera:
Używać:
źródło
Jest to bardzo proste dwukierunkowe wiązanie danych w javascript waniliowym ....
źródło
Późno na imprezę, szczególnie odkąd napisałem 2 libi związane miesiące / lata temu, wspomnę o nich później, ale nadal wydaje mi się odpowiedni. Krótko mówiąc, spoiler to technologie, które wybrałem:
Proxy
do obserwacji modeluMutationObserver
do śledzenia zmian DOM (ze względów wiążących, nie zmian wartości)addEventListener
procedury obsługiIMHO, oprócz PO, ważne jest, aby wdrożenie wiązania danych:
user.address.block
shift
,splice
i podobne)Biorąc to wszystko pod uwagę, moim zdaniem uniemożliwia rzucenie kilkudziesięciu linii JS. Próbowałem to zrobić raczej jako wzór niż lib - nie działało to dla mnie.
Następnie
Object.observe
usuwa się posiadanie , a jednak biorąc pod uwagę, że obserwacja modelu jest kluczową częścią - cała ta część MUSI być oddzielona zmartwieniem od innej biblioteki. Teraz do rzeczy, w których podjąłem ten problem - dokładnie tak, jak OP poprosił:Model (część JS)
Moim zdaniem do obserwacji modelu jest Proxy , jest to jedyny rozsądny sposób, aby działał, IMHO. W pełni funkcjonalna
observer
zasługuje na własną bibliotekę, więc opracowałemobject-observer
bibliotekę wyłącznie w tym celu.Model (y) należy zarejestrować za pomocą dedykowanego interfejsu API, w którym zamieniają się POJO
Observable
s, nie widać tutaj żadnego skrótu. Elementy DOM, które są uważane za powiązane widoki (patrz poniżej), są aktualizowane najpierw wartościami modelu / modeli, a następnie przy każdej zmianie danych.Widoki (część HTML)
IMHO, najczystszym sposobem wyrażenia wiązania, jest użycie atrybutów. Wielu zrobiło to wcześniej, a wielu zrobi później, więc nie ma tu żadnych wiadomości, jest to po prostu właściwy sposób, aby to zrobić. W moim przypadku zastosowałem następującą składnię:
<span data-tie="modelKey:path.to.data => targerProperty"></span>
ale jest to mniej ważne. Co to jest dla mnie ważne, brak złożonej składni skryptów w HTML - to znowu źle, IMHO.Najpierw gromadzone są wszystkie elementy wyznaczone jako widoki ograniczone. Z punktu widzenia wydajności zarządzanie wewnętrznym mapowaniem między modelami i widokami wydaje mi się nieuniknione, wydaje się właściwym przypadkiem, w którym pamięć + zarządzanie należy poświęcić, aby zapisać wyszukiwania i aktualizacje środowiska wykonawczego.
Widoki są aktualizowane na początku z modelu, jeśli są dostępne, a następnie z późniejszymi zmianami modelu, jak powiedzieliśmy. Co więcej, cały DOM powinien być obserwowany za pomocą
MutationObserver
, aby zareagować (powiązać / rozpiąć) na dynamicznie dodawanych / usuwanych / zmienianych elementach. Co więcej, wszystko to należy powielić w ShadowDOM (oczywiście jeden), aby nie pozostawiać niezwiązanych czarnych dziur.Lista szczegółów może rzeczywiście pójść dalej, ale moim zdaniem są to główne zasady, które sprawiłyby, że wiązanie danych byłoby realizowane z zachowaniem równowagi między kompletnością funkcji z jednej i zdrowej prostoty z drugiej strony.
I tak, oprócz
object-observer
wspomnianego powyżej, napisałem rzeczywiście takżedata-tier
bibliotekę, która implementuje wiązanie danych zgodnie z wyżej wymienionymi koncepcjami.źródło
W ciągu ostatnich 7 lat wiele się zmieniło. W większości przeglądarek mamy natywne komponenty sieciowe. IMO sedno problemu polega na współdzieleniu stanu między elementami, gdy tylko będzie to trywialne, aby zaktualizować interfejs użytkownika, gdy stan się zmienia i odwrotnie.
Aby współdzielić dane między elementami, możesz utworzyć klasę StateObserver i rozszerzyć z niej komponenty sieciowe. Minimalna implementacja wygląda mniej więcej tak:
bawić się tutaj
Podoba mi się to podejście, ponieważ:
data-
właściwościźródło