[JS] Jak obserwować zmiany obiektów w JavaScript? Natywny data-binding
Jak wszyscy wiemy, pisząc kod JavaScript dla naszej strony możemy wykorzystywać szereg eventów - zdarzeń DOM, które nas informują o tym co się dzieje z elementami na stronie. Mogą to być zdarzenia: load
, submit
, mousedown
i wiele innych.
Lecz to nie wszystko co możemy obserwować za pomocą JavaScriptu. Jedną z rzeczy mniej znanych, ale bardzo użytecznych jest obserwacja zmian stanów wartości atrybutów elementów DOM lub obserwacja zmian wartości obiektów JavaScript. Po przeczytaniu tego artykułu będziesz mógł/mogła wykorzystać ten sposób w swoich projektach webowych.
Obserwacja zmian wartości atrybutów DOM za pomocą JS
Aby móc obserować zmiany wartości atrybutów (np. data atrybuty, value, src, itd., itp) elementów DOM to musimy skorzystać z funkcjonalności o nazwie Mutation Observer. Za jego pomocą będziemy w stanie obserwować i reagować na zmiany w drzewie DOM.
Do obiektu MutationObserver
możemy przekazać odpowiedni zestaw parametrów, który pozwoli nam uzyskać informacje o zmianach które zachodzą w danym elemencie na stronie. Ich wartości zawsze ustawiamy albo na true
albo na false
. Domyślnie, zawsze będzie to false
.
Własność | Opis |
---|---|
childList |
Czy chcesz obserwować zmiany elementów DOM wewnątrz wybranego elementu? |
attributes |
Czy chcesz obserwować zmiany wartości atrybutów elementu DOM? |
characterData |
Czy chcesz obserwować zmiany tekstu wewnątrz elementu DOM? |
subtree |
Czy chcesz obserwować zmiany elementów DOM wewnątrz elementu oraz zmiany w DOM w elementach znajdujących się w wybranym elemencie. |
attributeOldValue |
Czy chcesz zapamiętać poprzednią wartość zmienionego atrybutu elementu? |
characterDataOldValue |
Czy chcesz zapamiętach poprzedni zmieniony tekst elementu? |
attributeFilter |
Jedyny przypadek, gdy zamiast wartości true lub false ustawiamy tablicę zawierającą listę atrybutów, które chcemy rzeczywiście obserwować. |
Jego zastosowanie wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var MutationObserver = window.MutationObserver || window.WebKitMutationObserver, attributeChangeCallback = function (mutations) { mutations.forEach(function (mutation) { console.log('mutation', mutation); [ ... jakikolwiek kod który ma być uruchomiony gdy zajdą zmiany wartości w atrybutach obiektów DOM ... ] }); }, observer = new MutationObserver(attributeChangeCallback), // nowa instancja MutationObserver domElement = document.querySelector('.element-to-observe'); // jakiś element DOM którego zmiany wartości atrybutów będziemy obserwować, np. '<div class=".element-to-observe" data-type="default"></div>' observer.observe(domElement, { attributes: true, attributeOldValue: true }); |
Mając powyższy kod, za każdym razem gdy zmieni się wartość atrybutu class
bądź data-type
, zostanie uruchomiona funkcja attributeChangeCallback
. Ta funkcja (callback) będzie mogła reagować na zmiany. W przypadku powyżej, w konsoli w przeglądarce pojawi log dotyczący pojedynczej mutacji, który będzie zawierał następujące informacje:
Własność | Typ własności | Opis |
---|---|---|
type |
String | Zwraca informację odnośnie typu mutacji. Jeśli zajdzie zmiana w atrybutach, to zwraca attributes . Jeśli zajdzie zmiana w tekście wewnątrz elementu, to zwróci informację characterData . Natomiast jeśli będzie to zmiana elementów DOM wewnątrz wybranego elementu, to własność będzie zawierała informację childList . |
target |
Node | Informacje o elemencie w którym zaszły zmiany. Jest to obiekt typu Node . |
addedNodes |
NodeList | Zwraca listę elementów DOM, które zostały dodane do wybranego elementu. Jeśli taka zmiana jest obserwowana. |
removedNodes |
NodeList | Zwraca listę elementów DOM, które zostały usunięte z wybranego elementu. Jeśli taka zmiana jest obserwowana. |
previousSibling |
Node | Zwraca obiekt dotyczący elementu poprzedzającego element dodany/usunięty lub null |
nextSibling |
Node | Zwraca obiekt dotyczący elementu następnego po elemencie dodanym/usuniętym lub null |
attributeName |
String | Zwraca nazwę zmienionego atrybutu lub null |
attributeNamespace |
String | Zwraca przestrzeń nazw w której znajduje się zmieniony atrybut lub null |
oldValue |
String | Zwraca poprzedni tekst, który został zastąpiony nowym wewnątrz elementu lub wartość atrybutu elementu. Nie zwraca on elementów DOM, które zostały zmienione. Aby uzyskać listę zmian w DOM należy wykorzystać removedNodes . |
Obserwacja zmian wartości elementów DOM za pomocą JS
Kod, który będzie obserwował zmiany w drzewie DOM wewnątrz elementu będzie wyglądał następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var MutationObserver = window.MutationObserver || window.WebKitMutationObserver, domChangeCallback = function (mutations) { mutations.forEach(function (mutation) { console.log('mutation', mutation); [ ... jakikolwiek kod który ma być uruchomiony gdy zajdą zmiany w drzewie DOM elementu ... ] }); }, observer = new MutationObserver(domChangeCallback), // nowa instancja MutationObserver domElement = document.querySelector('.element-to-observe'); // jakiś element DOM którego zmiany wartości atrybutów będziemy obserwować, np. '<div class=".element-to-observe" data-type="default"></div>' observer.observe(domElement, { childList: true, subtree: true }); |
Jak widać, kod nieznacznie się różni od poprzedniego kodu. Tak naprawdę główną różnicą będą parametry przekazane do obiektu MutationObserver oraz callback, który będzie obsługiwał wybrane zdarzenie.
W przypadku, gdybyśmy chcieli obserwować zmiany tekstu, to jako parametry obiektu możemy ustawić:
1 2 3 4 | textChangeParams = { characterData: true, characterDataOldValue: true }; |
I wstawiamy go jako drugi parametr funkcji observer.observe()
. Co ciekawe, w przeglądarce Firefox zmiany tekstu będą obserwowane nawet wtedy, gdy do observer.observe()
przekażemy parametry dotyczące childList
oraz subtree
.
Obserwacja zmian wartości właśności obiektów za pomocą JS
Jeśli zdarzyło Ci się korzystać z Angular.js lub jakiegokolwiek innego frameworka JavaScript gdzie jest zaimplementowany data-binding, to z pomocą funkcjonalności obserwowania zmian w obiektach - Object.observe()
będziesz w stanie osiągnąć niemal to samo zachowanie. Twoja aplikacja będzie w stanie reagować na zmiany wartości w obiektach.
Dla przykładu, wyibraźmy sobie że mamy obiekt z danymi użytkownika. Po każdej zmianie nazwy użytkownika będziemy chcieli zaktualizować napis wyświetlany na stronie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | var user = { name: 'Piotr Nalepa', info: {} }, userNameContainer = document.querySelector('#username'), classNameChanged = '.name-changed', userNameChangedTimeout, updateUserName = function () { userNameContainer.innerHTML = user.name; userNameContainer.classList.add(classNameChanged); window.clearTimeout(userNameChangedTimeout); userNameChangedTimeout = window.setTimeout(function () { userNameContainer.classList.remove(classNameChanged); }, 500); }, objectChangedCallback = function (changes) { changes.forEach(function(change) { if (change.name === 'name') { updateUserName(); } }); }; updateUserName(); Object.observe(user, objectChangedCallback); |
Nic nie stoi na przeszkodzie, aby obserwowanie zmian obiektów sprzężyć z obserwowaniem zmian w DOM. W ten sposób możemy osiągnąć tzw. 2-way data-binding.
Funkcja Object.observe()
przyjmuje 3 parametry:
Nazwa | Opis |
---|---|
obj |
Obiekt który chcemy obserwować |
callback |
Funkcja, która będzie uruchamiana przy każdej zmianie wartości obiektów. Funkcja jako parametr przyjmuje tablicę obiektów z informacjami o zmianach w wybranym obiekcie. Obiekt z informacjami o zmianach posiada następujące informacje:
|
acceptList |
Lista typów zmian, które mają być obserwowane w danym obiekcie. Domyślnie, wartość tego parametru to: ['add', 'update', 'delete', 'reconfigure', 'setPrototype', 'preventExtensions'] . |
Polyfille
Aby móc korzystać z pełni możliwości w większości przeglądarek, niezbędne będą dodatkowe bilioteki, tzw. polyfille, które uzupełnią brakujące funkcjonalności w przeglądarkach.
Mutation Observer polyfill dla DOM
Jeśli chodzi obserwowanie zmiany w drzewie DOM elementów, to polecanym polyfillem będzie biblioteka od ludzi zajmujących się Web Components.
Object.observe polyfill
Natomiast, dla Object.observe()
polecanym polyfillem będzie biblioteka stworzona przez twórców Polymera.
Podsumowanie
Mam nadzieję, że funkcjonalności, które omówiłem w tym wpisie pozwolą Ci na pisanie jeszcze ciekawszych skryptów JavaScript niż do tej pory. Za ich pomocą, w wielu przypadkach, nie trzeba korzystać żadnych zewnętrznych narzędzi aby mieć możliwość korzystania z tych ficzerów.
Co do wsparcia przeglądarek to dla Mutation Observer wsparcie wygląda następująco:
Natomiast, jeśli chodzi o Object.observe() to z tym jest gorzej. Ze względu na to, że jest to część składowa standardu ECMAScript 7 to jego wsparcie w przeglądarkach wygląda dość mizernie. Generalnie, przeglądarki z silnikiem Webkit wspierają ten standard, a wszystkie inne wymagają dodatkowych bibliotek (polyfilli):