Jak używać $scope.$watch I $scope.$apply in AngularJS?

Nie rozumiem jak używać $scope.$watch i $scope.$apply. Oficjalna dokumentacja nie jest pomocna.

Czego konkretnie nie rozumiem:

    Czy są połączone z DOM?
  • Jak mogę zaktualizować zmiany DOM w modelu?
  • jaki jest punkt połączenia między nimi?

Próbowałem tego poradnika , ale zrozumienie $watch i $apply jest czymś oczywistym.

Co robić $apply i $watch i jak ich używać odpowiednio?

Author: Peter Mortensen, 2013-02-27

7 answers

Aby to zrozumieć, musisz mieć świadomość, jak działa AngularJS.

Cykl Digest i $scope

Przede wszystkim AngularJS definiuje pojęcie tzw. cyklu digestowego . Cykl ten można uznać za pętlę, podczas której AngularJS sprawdza, czy istnieją jakiekolwiek zmiany we wszystkich zmiennych obserwowanych przez wszystkie $scope s. Więc jeśli masz $scope.myVar zdefiniowany w kontrolerze i ta zmienna została oznaczona jako obserwowana , to jesteś domyślnie mówi AngularJS, aby monitorował zmiany na myVar w każdej iteracji pętli.

Naturalnym pytaniem następczym byłoby: czy wszystko, co jest związane z $scope, jest obserwowane? Na szczęście nie. Gdybyś obserwował zmiany w każdym obiekcie w swojej $scope, szybko pętla digest zajęłaby wieki, aby ocenić i szybko napotkałbyś problemy z wydajnością. Dlatego zespół AngularJS dał nam dwa sposoby deklarowania jakiejś zmiennej $scope jako obserwowanej (Czytaj poniżej).

$zegarek pomaga słuchać zmian $ zakresu

Istnieją dwa sposoby deklarowania zmiennej $scope jako obserwowanej.

  1. używając go w szablonie za pomocą wyrażenia <span>{{myVar}}</span>
  2. poprzez dodanie go ręcznie za pomocą usługi $watch

Ad 1) Jest to najczęstszy scenariusz i jestem pewien, że widziałeś go wcześniej, ale nie wiedziałeś, że to stworzyło zegarek w tle. Tak, było! Używanie dyrektyw AngularJS (np. ng-repeat) może również tworzyć Ukryte zegarki.

Ad 2) W ten sposób tworzysz własne zegarki. $watch usługa pomaga uruchomić kod, gdy jakaś wartość dołączona do $scope uległa zmianie. Jest rzadko używany, ale czasami jest pomocny. Na przykład, jeśli chcesz uruchomić kod za każdym razem, gdy zmieni się "myVar", możesz wykonać następujące czynności:

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
        alert('hey, myVar has changed!');
    });

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // This will trigger $watch expression to kick in
    };
}

$apply umożliwia integrację zmian z cyklem digest

Możesz myśleć o $apply funkcja jako całkowanie mechanizm . Widzisz, za każdym razem gdy zmieniasz jakąś obserwowaną zmienną dołączoną do $scope obiekt bezpośrednio, AngularJS będzie wiedział, że zmiana nastąpiła. Dzieje się tak, ponieważ AngularJS już wiedział, aby monitorować te zmiany. Jeśli więc dzieje się to w kodzie zarządzanym przez framework, cykl digest będzie kontynuowany.

Jednak czasami chcesz zmienić jakąś wartość poza światem AngularJS i zobaczyć, jak zmiany propagują się normalnie. Rozważ to-masz $scope.myVar wartość, która zostanie zmodyfikowana w jQuery ' s $.ajax(). Stanie się to w pewnym momencie w przyszłości. AngularJS nie może się doczekać, aby to się stało, ponieważ nie został poinstruowany, aby czekać na jQuery.

Aby temu zaradzić, $apply został wprowadzony. Pozwala to wyraźnie rozpocząć cykl trawienia. Należy jednak użyć tego tylko do migracji niektórych danych do AngularJS( integracja z innymi frameworkami), ale nigdy nie używać tej metody w połączeniu ze zwykłym kodem AngularJS, ponieważ AngularJS wyrzuci więc błąd.

Jak to wszystko ma związek z DOM?

Cóż, naprawdę powinieneś ponownie podążać za samouczkiem, teraz, gdy wiesz o tym wszystkim. Cykl digest upewni się, że interfejs użytkownika i kod JavaScript pozostają zsynchronizowane, oceniając każdą obserwację dołączoną do wszystkich $scope s, o ile nic się nie zmieni. Jeśli w pętli digest nie nastąpi więcej zmian, to uważa się ją za zakończoną.

Do obiektu $scope można dołączać obiekty jawnie w Kontrolera, lub deklarując je w postaci {{expression}} bezpośrednio w widoku.

Mam nadzieję, że to pomoże wyjaśnić jakąś podstawową wiedzę na ten temat.

Dalsze odczyty:

 1661
Author: ŁukaszBachman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-19 11:47:30

W AngularJS aktualizujemy nasze modele, a nasze widoki / szablony aktualizują DOM "automatycznie" (poprzez wbudowane lub niestandardowe dyrektywy).

$apply I $watch, obie jako metody Scope, nie są powiązane z DOM.

Strona Concepts (sekcja "Runtime") ma całkiem dobre wyjaśnienie pętli $digest, $apply, kolejki $evalAsync i listy $watch. Oto zdjęcie, które towarzyszy tekstowi:

$digest loop

Jakikolwiek kod ma dostęp do zakresu – Zwykle kontrolery i dyrektywy (ich funkcje łącza i/lub ich kontrolery) – mogą ustawić "watchExpression", które AngularJS będzie oceniał w odniesieniu do tego zakresu. Ta ocena ma miejsce za każdym razem, gdy AngularJS wchodzi w pętlę $digest (w szczególności pętlę "$watch list"). Można obserwować poszczególne właściwości zakresu, można zdefiniować funkcję do oglądania dwóch właściwości razem, można obserwować długość tablicy, itd.

Gdy dzieje się coś "wewnątrz AngularJS" – np. wpisujesz w polu tekstowym z włączoną dwukierunkową bazą danych AngularJS (tzn. używa modelu ng) wywołane zostanie wywołanie zwrotne $http, itd. - $apply został już wywołany, więc jesteśmy wewnątrz prostokąta" AngularJS " na powyższym rysunku. Wszystkie wyrażenia watchExpressions zostaną ocenione (prawdopodobnie więcej niż jeden raz-dopóki nie zostaną wykryte żadne dalsze zmiany).

Gdy dzieje się coś "poza AngularJS" – np. użyłeś bind () w dyrektywie i wtedy to zdarzenie zostanie wywołane, powodując wywołanie zwrotne lub niektóre jQuery zarejestrowane wywołania zwrotne-nadal jesteśmy w" natywnym " prostokącie. Jeśli kod wywołania zwrotnego modyfikuje cokolwiek, co ogląda dowolny $watch, wywołaj $apply, aby dostać się do prostokąta AngularJS, powodując uruchomienie pętli $digest, a tym samym AngularJS zauważy zmianę i zrobi jej magię.

 152
Author: Mark Rajcok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-02 08:17:08

Ten blog został omówiony wszystko, co tworzy przykłady i zrozumiałe wyjaśnienia.

Funkcje AngularJS $scope $watch(), $digest() i $apply() to niektóre z centralnych funkcji w AngularJS. Zrozumienie $watch(), $digest() i $apply() jest niezbędna do zrozumienia AngularJS.

Kiedy tworzysz powiązanie danych z jakiegoś miejsca w widoku do zmiennej w obiekcie $scope, AngularJS tworzy wewnętrznie "watch". Zegarek oznacza, że Zegarki AngularJS zmieniają się w zmienna na $scope object. Framework "obserwuje" zmienną. Zegarki są tworzone przy użyciu funkcji $scope.$watch(), którą omówię w dalszej części tego tekstu.

W kluczowych punktach aplikacji AngularJS wywołuje funkcję $scope.$digest(). Ta funkcja jest powtarzana przez wszystkie zegarki i sprawdza, czy którakolwiek z obserwowanych zmiennych uległa zmianie. Jeśli zmienna obserwowana uległa zmianie, wywoływana jest odpowiednia funkcja listener. Funkcja słuchacza wykonuje każdą pracę, jaką musi wykonać, na przykład zmieniając tekst HTML odzwierciedlający nową wartość obserwowanej zmiennej. Tak więc funkcja $digest() uruchamia powiązanie danych do aktualizacji.

Przez większość czasu AngularJS wywoła $scope.funkcje $watch () i $scope.$digest() są dla ciebie, ale w niektórych sytuacjach możesz je wywołać samodzielnie. Dlatego naprawdę dobrze jest wiedzieć, jak działają.

Funkcja $scope.$apply() jest używana do wykonania jakiegoś kodu, a następnie wywołania $scope.$digest(), więc wszystkie zegarki są sprawdzane i odpowiadający im zegarek funkcje słuchacza są wywoływane. Funkcja $apply() jest przydatna przy integracji AngularJS z innym kodem.

W dalszej części tekstu przedstawię więcej szczegółów na temat funkcji $watch(), $digest() i $apply().

$ watch ()

Funkcja $scope.watch() tworzy obserwację pewnej zmiennej. Po zarejestrowaniu zegarka przekazujesz dwie funkcje jako parametry do funkcji $watch():

  • funkcja wartości
  • funkcja słuchacza

Oto przykład:

$scope.$watch(function() {},
              function() {}
             );

Pierwsza funkcja jest funkcją wartości, a druga funkcją słuchacza.

Funkcja value powinna zwracać wartość, która jest obserwowana. AngularJS może następnie sprawdzić wartość zwróconą względem wartości, którą funkcja watch zwróciła ostatnio. W ten sposób AngularJS może określić, czy wartość się zmieniła. Oto przykład:

$scope.$watch(function(scope) { return scope.data.myVar },
              function() {}
             );

Ten przykład funkcja valule zwraca zmienną $scope scope.data.myVar. Jeżeli wartość tej zmiennej zmiany, zostanie zwrócona inna wartość, a AngularJS wywoła funkcję listener.

Zwróć uwagę, jak funkcja value przyjmuje zakres jako parametr (bez $ w nazwie). Za pomocą tego parametru funkcja value może uzyskać dostęp do $scope i jej zmiennych. Funkcja value może również obserwować zmienne globalne, jeśli tego potrzebujesz, ale najczęściej oglądasz zmienną $scope.

Funkcja listener powinna robić to, co trzeba, jeśli wartość się zmieniła. Być może musisz zmienić zawartość innej zmiennej lub ustawić zawartość elementu HTML lub czegoś takiego. Oto przykład:

$scope.$watch(function(scope) { return scope.data.myVar },
              function(newValue, oldValue) {
                  document.getElementById("").innerHTML =
                      "" + newValue + "";
              }
             );

Ten przykład ustawia wewnętrzny HTML elementu HTML na nową wartość zmiennej, osadzoną w elemencie b, co powoduje pogrubienie wartości. Oczywiście mogłeś to zrobić używając kodu {{ data.myVar }, ale to tylko przykład tego, co możesz zrobić wewnątrz funkcji listener.

$ digest ()

$scope.$digest() funkcja iteracji przez wszystkie zegarki w $scope object, oraz jego obiekty potomne $scope (jeśli takie posiadają). Gdy $digest() iteruje nad zegarami, wywołuje funkcję value dla każdego zegarka. Jeśli wartość zwracana przez funkcję value jest inna niż wartość zwracana przy ostatnim wywołaniu, to funkcja listener dla tego zegarka jest wywoływana.

Funkcja $digest() jest wywoływana, gdy AngularJS uważa, że jest to konieczne. Na przykład po wykonaniu przycisku click handler lub po powrocie wywołania AJAX (po wykonaniu funkcji zwrotnej done () / fail ()).

Możesz napotkać przypadki narożne, w których AngularJS nie wywołuje dla ciebie funkcji $digest(). Zazwyczaj zauważysz to, zauważając, że powiązania danych nie aktualizują wyświetlanych wartości. W takim przypadku zadzwoń $scope.$digest() i powinno działać. Możesz też zamiast tego użyć $scope.$apply(), co wyjaśnię w następnej sekcji.

$ apply ()

Funkcja $scope.$apply() przyjmuje funkcję jako parametr, który jest wykonywany, a po tym $scope.$digest() jest wywoływany wewnętrznie. Ułatwia to sprawdzenie wszystkich zegarków, a tym samym odświeżenie wszystkich wiązań danych. Oto przykład $apply():

$scope.$apply(function() {
    $scope.data.myVar = "Another value";
});

Funkcja przekazana do funkcji $apply() jako parametr zmieni wartość $scope.data.myVar. Gdy funkcja zakończy działanie AngularJS wywoła funkcję $scope.$digest(), więc wszystkie zegarki są sprawdzane pod kątem zmian obserwowanych wartości.

Przykład

Aby zilustrować jak $watch(), $digest() oraz $apply() działa, spójrz na ten przykład:

<div ng-controller="myController">
    {{data.time}}

    <br/>
    <button ng-click="updateTime()">update time - ng-click</button>
    <button id="updateTimeButton"  >update time</button>
</div>


<script>
    var module       = angular.module("myapp", []);
    var myController1 = module.controller("myController", function($scope) {

        $scope.data = { time : new Date() };

        $scope.updateTime = function() {
            $scope.data.time = new Date();
        }

        document.getElementById("updateTimeButton")
                .addEventListener('click', function() {
            console.log("update time clicked");
            $scope.data.time = new Date();
        });
    });
</script>

Jego przykład wiąże zmienną $scope.data.time Z Dyrektywą interpolacyjną, która łączy wartość zmiennej ze stroną HTML. To Wiązanie tworzy wewnętrzny zegarek na $scope.data.time variable.

Przykład zawiera również dwa przyciski. Pierwszy przycisk ma dołączony słuchacz ng-click. Po kliknięciu tego przycisku wywoływana jest funkcja $scope.updateTime(), a następnie funkcja AngularJS wywołuje $scope.$digest(), dzięki czemu powiązania danych są aktualizowane.

Drugi przycisk pobiera standardowy odbiornik zdarzeń JavaScript dołączony do niego z wnętrza funkcji kontrolera. Po kliknięciu drugiego przycisku funkcja listener jest wykonywana. Jak widać, funkcje słuchacza dla obu przycisków działają prawie tak samo, ale po wywołaniu funkcji słuchacza drugiego przycisku powiązanie danych nie jest aktualizowane. Dzieje się tak dlatego, że $scope.$digest() nie jest wywoływany po wykonaniu detektora zdarzeń drugiego przycisku. Tak więc po kliknięciu drugiego przycisku Czas jest aktualizowany w $scope.data.time zmienna, ale nowy czas nigdy nie jest wyświetlany.

Aby to naprawić, możemy dodać $scope.$digest() wywołanie do ostatniej linii słuchacza zdarzeń przycisku, Tak:

document.getElementById("updateTimeButton")
        .addEventListener('click', function() {
    console.log("update time clicked");
    $scope.data.time = new Date();
    $scope.$digest();
});

Zamiast wywoływania $digest() wewnątrz funkcji słuchacza przycisków można było również użyć funkcji $apply() w następujący sposób:

document.getElementById("updateTimeButton")
        .addEventListener('click', function() {
    $scope.$apply(function() {
        console.log("update time clicked");
        $scope.data.time = new Date();
    });
});

Zwróć uwagę, jak funkcja $scope.$apply() jest wywoływana z wnętrza detektora zdarzeń przycisku i jak aktualizacja zmiennej $scope.data.time jest wykonywana wewnątrz funkcji przekazywanej jako parametr do funkcja $apply(). Gdy funkcja $apply() zakończy wewnętrzne wywołania AngularJS $digest(), wszystkie powiązania danych są aktualizowane.

 56
Author: Alex Jolig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-22 10:13:37

AngularJS rozszerza tę pętlę zdarzeń , tworząc coś o nazwie AngularJS context.

$zegarek()

Za każdym razem, gdy wiążesz coś w interfejsie, wstawiasz $watch na liście $watch .

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

Tutaj mamy $scope.user, który jest związany z pierwszym wejściem, i mamy $scope.pass, który jest związany z drugim. W ten sposób dodajemy dwa $watches do listy $watch .

Gdy naszszablon zostanie załadowany, czyli w fazie linkowania, kompilator będzie szukał każdej dyrektywy i stworzy wszystkie $watches, które są potrzebne.

AngularJS zapewnia $watch, $watchcollection i $watch(true). Poniżej znajduje się zgrabny schemat wyjaśniający wszystkie trzy wzięte z obserwatorów w głębi .

Tutaj wpisz opis obrazka

angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
  $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];

  $scope.$watch("users", function() {
    console.log("**** reference checkers $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collection  checkers $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** equality checkers with $watch(true) ****")
  }, true);

  $timeout(function(){
     console.log("Triggers All ")
     $scope.users = [];
     $scope.$digest();

     console.log("Triggers $watchCollection and $watch(true)")
     $scope.users.push({ name: 'Thalaivar'});
     $scope.$digest();

     console.log("Triggers $watch(true)")
     $scope.users[0].name = 'Superstar';
     $scope.$digest();
  });
}

Http://jsfiddle.net/2Lyn0Lkb/

$digest pętla

Gdy przeglądarka otrzyma zdarzenie, które może być zarządzane przez kontekst AngularJS, zostanie wywołana pętla $digest. Pętla ta jest wykonana z dwóch mniejszych pętli. Jeden przetwarza kolejkę $evalAsync, a drugi przetwarza kolejkę $watch list. $digest przejrzy listę $watch, którą mamy

app.controller('MainCtrl', function() {
  $scope.name = "vinoth";

  $scope.changeFoo = function() {
      $scope.name = "Thalaivar";
  }
});

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

Tutaj mamy tylko jeden $watch ponieważ ng-click nie tworzy żadnych zegarków.

Naciskamy przycisk.

    Przeglądarka otrzymuje zdarzenie, które wejdzie do kontekstu AngularJS.]}
  1. pętla $digest uruchomi się i zapyta każdego $watch o zmiany.
  2. od $watch Który Czuwał Nad zmiany w $scope.name zgłosi zmianę, to wymusi kolejną pętlę $digest.
  3. nowa pętla nic nie zgłasza.
  4. przeglądarka odzyskuje kontrolę i zaktualizuje DOM odzwierciedlając nową wartość $scope.name
  5. ważne jest to, że każde zdarzenie, które wejdzie w kontekst AngularJS, uruchomi pętlę $digest. Oznacza to, że za każdym razem, gdy napiszemy literę na wejściu, pętla uruchomi sprawdzanie każdego $watch w tym strona.

$ apply ()

Jeśli wywołasz $apply kiedy zdarzenie zostanie wywołane, przejdzie ono przez kontekst kątowy, ale jeśli go nie wywołasz, będzie działać poza nim. To takie proste. $apply wezwie $digest() pętla wewnętrznie i będzie iteracją dla wszystkich zegarków, aby upewnić się, że DOM jest zaktualizowany o nowo zaktualizowaną wartość.

Metoda $apply() wywoła obserwatorów na całym łańcuchu $scope, podczas gdy metoda $digest() wywoła obserwatorów tylko na prąd $scope i jego children. gdy żaden z wyżej wymienionych obiektów nie musi wiedzieć o lokalnych zmianach, możesz użyć $digest().

 37
Author: Thalaivar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-07 15:14:54

Istnieją również $watchGroup i $watchCollection. W szczególności, $watchGroup jest bardzo pomocne, jeśli chcesz wywołać funkcję do aktualizacji obiektu, który ma wiele właściwości w widoku, który nie jest obiektem dom, na przykład dla innego widoku w canvas, webGL lub żądania serwera. Tutaj, Dokumentacja link .

 17
Author: Utkarsh Bhardwaj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-08 05:29:37

Znalazłem bardzo dogłębne filmy, które obejmują $watch, $apply, $digest i cykl trawienia w:

Poniżej znajduje się kilka slajdów używanych w tych filmach, aby wyjaśnić pojęcia(na wszelki wypadek, jeśli powyższe linki są usunięte / nie działa).

Tutaj wpisz opis obrazka

Na powyższym obrazku" $ scope.c" nie jest obserwowany, ponieważ nie jest używany w żadnym z powiązań danych (w znacznikach). Pozostałe dwa ($scope.a i $scope.b) będą oglądane.

Tutaj wpisz opis obrazka

Z powyższego obrazka: na podstawie odpowiedniego zdarzenia przeglądarki, AngularJS rejestruje zdarzenie, wykonuje cykl digest (przechodzi przez wszystkie zegarki dla zmian), wykonuje funkcje zegarka i aktualizuje DOM. Jeśli nie zdarzenia przeglądarki, cykl digest może być uruchomiony ręcznie za pomocą $apply lub $digest.

Więcej o $apply i $digest:

Tutaj wpisz opis obrazka

 15
Author: user203687,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-07 15:08:25

Po prostu skończ czytać wszystkie powyższe, nudne i senne (przepraszam, ale to prawda). Bardzo techniczne, dogłębne, szczegółowe i suche. Dlaczego piszę? Ponieważ AngularJS jest ogromny, wiele wzajemnie powiązanych pojęć może sprawić, że każdy oszaleje. Często zadawałem sobie pytanie, czy nie jestem wystarczająco mądry, aby je zrozumieć? Nie! To dlatego, że tak niewielu potrafi wyjaśnić technologię w for-dummie language bez wszystkich terminologii! Ok, pozwól mi spróbować:

1) wszystkie one są związane z wydarzeniami. (słyszę śmiej się, ale czytaj dalej)

Jeśli nie wiesz, co to jest event-driven, to myślę, że umieścisz przycisk na stronie, podłącz go w / A funkcji za pomocą "on-click", czekając na użytkowników, aby go kliknąć, aby wywołać działania, które sadzisz wewnątrz funkcja. Lub pomyśl o "wyzwalaniu" SQL Server / Oracle.

2) $zegarek jest "on-click".

Co jest szczególne w tym, że przyjmuje 2 Funkcje jako parametry, pierwsza podaje wartość Z zdarzenia, drugi bierze się wartość do rozważanie...

3) $digest jest bossem, który niestrudzenie sprawdza okolice , bla-bla-bla, ale dobry szef.

4) $Zastosuj daje Ci sposób, gdy chcesz zrobić to ręcznie, Jak zabezpieczenie przed awarią (jeśli on-click nie działa, wymusisz jego uruchomienie.)

Zróbmy to wizualnie. Wyobraź to sobie, aby jeszcze łatwiej było uchwycić pomysł:

W restauracji,

- kelnerzy są ma przyjmować zamówienia od klientów, jest to

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

- MANAGER bieganie po okolicy, aby upewnić się, że wszyscy kelnerzy są przytomni, reagują na wszelkie oznaki zmian od klientów. To jest $digest()

- właściciel ma ostateczną moc kierowania wszystkich na życzenie, to jest $apply()

 10
Author: Jeb50,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-07 01:20:51