Ha(c)ki na… HTML 5


Hack No.46

Osadź SVG bezpośrednio w swoim HTML

Możesz osadzić SVG bezpośrednio w pliku HTML, eliminując potrzebę stosowania zewnętrznego pliku .svg. Dzięki HTML5 Twoje elementy SVG mogą znajdować się w tym samym DOM, co HTML, i usuniesz niektóre bariery związane z oddzielnym zarządzaniem dwoma bazami kodu. SVG jest potężny i może być dość złożony, tworząc nieograniczone ilustracje i animacje za pomocą prostego języka opartego na XML. Ale w niektórych przypadkach możesz mieć tylko prostą ilustrację, która nie wymaga rygoru zewnętrznego pliku do zarządzania kodem. Tak jak HTML5 umożliwia umieszczanie obrazów bezpośrednio w znacznikach, tak również SVG można osadzać bezpośrednio w kodzie HTML. Patrząc na kod, który jest z tym związany, możesz zobaczyć, że jest to dokładnie to, czego się spodziewałeś. Mamy naszą stronę HTML i zamiast używać tagu wskazującego na zewnętrzny plik SVG, widzimy całą zawartość wcześniej zewnętrznego pliku SVG bezpośrednio w naszym HTML. W naszym przykładzie użyjemy naszej sprawdzonej, starej, uśmiechniętej ilustracji SVG osadzonej bezpośrednio w naszym kodzie HTML:

< doctype !html >

< html >

< head >

< meta charset="utf-8" >

< title >SVG Sample< /title >

< link href="assets/css/bootstrap.css" rel="stylesheet" / >

< link href="assets/css/bootstrap-responsive.css" rel="stylesheet" / >

< head >

< body >

< div class="navbar... ..."< /div >

< h1 > My Inline SVG Sample< /h1 >

< div id="svgWrapper" class="row" >

< svg version="1.1" baseProfile="full" xmlns=http://www.w3.org/2000/svg >

< circle cx="300" cy="164" r="160" fill="yellow" stroke="black" stroke-width="2" / >

< circle cx="210" cy="100" r="20" fill="black" / >

< circle cx="380" cy="100" r="20" fill="black" / >< br>
< clipPath id="MyClip" >

< rect x="30" y="200" width="600" height="100" / >

< /clipPath >

< circle cx="300" cy="160" r="120" fill-opacity="0" stroke="black" stroke-width="5" clip-path="url(#MyClip)" / >

< /svg >

< /div >

< /body >

< /html

> W poprzednim przykładzie, gdy mechanizm renderujący zobaczy znacznik deklaracji SVG, przełącza parsery z HTML na SVG. Kiedy tag się zamyka, wraca z SVG do HTML. Nasze wyniki wyglądają dokładnie tak, jak wtedy, gdy plik SVG znajdował się w zewnętrznym pliku.

Dlaczego Inline?

Oprócz oczywistego argumentu "łatwość użycia", umieszczenie SVG w tekście ma dodatkowe zalety. Przede wszystkim wbudowany SVG może być użyty jako zwiększenie wydajności. W niektórych scenariuszach możesz skorzystać z braku konieczności ładowania zewnętrznego pliku (ponieważ będzie to wymagało dodatkowego wywołania twojego serwera) i możesz umieścić kod SVG bezpośrednio na stronie. Należy pamiętać, że nie zawsze jest to poprawa wydajności, zwłaszcza gdy masz dużą ilość kodu SVG, który Twoja przeglądarka może załadować równolegle z elementami, aby zaoszczędzić czas. W przypadkach takich jak nasz, gdy ilustracja składa się tylko z kilku wierszy kodu, generalnie lepiej będzie pod względem wydajności wstawić ją i usunąć dodatkowe wywołanie. Dodatkową zaletą wbudowanego SVG może być odniesienie do DOM. Ogólnie rzecz biorąc, plik SVG jest oddzielnym elementem DOM od strony, gdy jest ładowany do zewnętrznego pliku SVG (traktuj go jako element iframe, element HTML, który ładuje oddzielną stronę wewnątrz elementu). W związku z tym każdy kod CSS, który ma wpływ na Twój SGV, musi być umieszczony w pliku SVG lub linkowany z niego, a zatem nie można go zastosować do kodu HTML ani strony. Podobnie JavaScript musi uzyskiwać dostęp do elementów SVG za pośrednictwem obiektu SVG i podlega tym samym ograniczeniom, co dostęp do elementów w ramce iframe. Przeniesienie SVG do DOM bezpośrednio usuwa te bariery i pozwala naprawdę traktować elementy SVG tak jak każdy inny element DOM. Spójrzmy na szybki i dziwaczny przykład wpływu deklaracji CSS na wbudowane SVG. W naszym przykładzie mamy prostą deklarację CSS:

< style >

circle {

stroke: red;

stroke-width: 12px;

}

< /style >

Ten blok stylu jest osadzony bezpośrednio na naszej stronie HTML, a na naszej stronie mamy dwie buźki SVG. Pierwsza ściana jest ładowana jako zewnętrzny obraz SVG, a druga jako wbudowany obraz SVG. Jak widać, CSS nie ma zastosowania do żadnego z kręgów w naszym osadzonym SVG ani do każdego koła w naszym wbudowanym SVG. Osadzony SVG nie zawsze jest najlepszym wyborem dla Twoich dokumentów, ale zawsze dobrze jest mieć opcje.

Hack No.47

Zapewnij możliwość przeciągania treści w swojej aplikacji

Zaledwie kilka wierszy kodu może zamienić dowolny element na stronie w element do przeciągania. HTML5 sprawia, że funkcja "przeciągnij i upuść" jest obywatelem pierwszej klasy dzięki łatwej implementacji i zupełnie nowym funkcjom, które pomagają dokładnie wiedzieć, co dzieje się z Twoją aplikacją internetową. Dawno minęły czasy pisania setek wierszy kodu, aby umożliwić użytkownikom przeciąganie elementu z jednej części strony do drugiej. HTML5 wykorzystał ten pomysł i przedstawia nam nowoczesną wersję tej odwiecznej interakcji. Dzięki HTML5 implementacja jest łatwa, a dane wokół niej są bogate. Zacznijmy od przyjrzenia się, jak łatwo jest umożliwić przeciąganie dowolnego elementu na stronie:

< div id = "myDraggableItem" draggable = "true" >

to jest treść, którą chcę przeciągać po ekranie

< /div >

I masz to! Czy nie powiedziałem, że to było łatwe? OK, gdyby to było wszystko, co trzeba zrobić z przeciąganiem i upuszczaniem, ten hack byłby skończony. Czytaj dalej, a zobaczysz, że ten hack dostarcza mnóstwa informacji, które ułatwią Ci zaimplementować tę funkcję w swoich aplikacjach już dziś.

Włączanie przeciągania

Przeciąganie i upuszczanie może nie być tak obce dla użytkowników. W wielu przeglądarkach funkcja przeciągania i upuszczania jest domyślnie włączona dla niektórych elementów strony, głównie tagów kotwic i tagów graficznych. Te dwie pozycje mają wyraźne wskazówki do zasobów, z którymi są powiązane. Znacznik zakotwiczenia ma href, który może łatwo stać się zakładką po przeciągnięciu przez pasek zakładek lub skrótem po przeciągnięciu do systemu operacyjnego. Skrót tworzony po przeciągnięciu łącza na pulpit. Obrazy mają podobne zachowanie. Ponieważ znacznik obrazu jest powiązany bezpośrednio z plikiem, można go również przeciągać. Pamiętaj, że nie każdy obraz w aplikacji internetowej jest tagiem obrazu. Wiele obrazów jest osadzonych w CSS z obrazami tła lub jest tworzonych za pomocą tagów SVG lub Canvas. Ten nieodłączny atrybut, który można przeciągać, odnosi się konkretnie do znacznika obrazu. Dodanie tego zachowania do dowolnego elementu jest łatwe. Po prostu dodajesz atrybut draggable do elementu strony i nagle staje się on "przeciągalny"! Przenieśmy tę możliwość na wyższy poziom i spójrzmy na wszystkie wydarzenia, które są publikowane, gdy chwytamy się tego elementu. Potem coś zrobimy z tymi wydarzeniami. Zacznijmy od kilku standardowych znaczników. Będziemy mieć kilka okręgów na stronie, które są tworzone przez dodanie fantazyjnych CSS do niektórych elementów div oraz "kosza na śmieci" utworzonego z innego fantazyjnego elementu div. Oto znaczniki, których używamy:

< div class = "row" >

< div class = "span-6 dragTarg" > < /div >

< /div >

< div class = "row" >

< div class = "span-6 dragItems" >

< div draggable = "true" class = "red" > < /div >

< div draggable = "true" class = "green" > < /div >

< div draggable = "true" class = "blue" > < /div >

< /div >

< /div >

To całkiem proste, a kiedy dodamy CSS. W tym momencie możemy podnieść cele i przeciągnąć je po stronie, ale gdy tylko je upuścimy, wrócą do swoich pierwotnych pozycji. Bez słuchaczy, którzy mogliby złapać, co się dzieje, nie ma się czym hakować. Słuchanie tych wszystkich wspaniałych wydarzeń HTML5 naprawdę przenosi zdarzenia DOM (Document Object Model) na wyższy poziom, dostarczając dogłębne zdarzenia dotyczące wszystkich interakcji. Samo przeciągnij i upuść ma powiązane z nim następujące zdarzenia:

dragstart

drag

dragenter

dragleave

dragover

drop

dragend

Przyjrzyjmy się kilku z tych wydarzeń, stosując słuchaczy. Aby to osiągnąć, dodamy kilka wierszy JavaScript do tagu skryptu na naszej stronie. Zaczniemy od dodania kilku słuchaczy do elementów, które będzie można przeciągać:

var circle = document.querySelectorAll ('. dragItems div');

for (var i = 0; i
circle [i] .addEventListener ('dragstart', startDrag, false);

circle [i] .addEventListener ('dragend', endDrag, false);

}

Zaczęliśmy od selektora zapytania, który tworzy kolekcję naszych kręgów, a następnie przejrzeliśmy każdy z nich, aby dodać do nich dwóch różnych detektorów: pierwszy nasłuchuje zdarzenia, które jest publikowane, gdy po raz pierwszy zaczynamy przeciągać element, a drugi nasłuchuje zdarzenia, gdy przestajemy przeciągać element. Każdy słuchacz wywoła swoją odpowiednią funkcję:

function startDrag (event) {

this.style.border = '5px jednolity różowy';

}

function endDrag (event) {

this.style.display = 'none';

}

Tutaj dodaliśmy trochę dodatkowego kontekstu do elementów podczas przeciągania, aby podkreślić, który element jest przenoszony. W tym przypadku, kiedy zaczynamy przeciągać nasz okrąg, zmieniamy obramowanie na różową przerywaną linię, a kiedy kończymy przeciąganie, dodajemy display = 'none' do elementu div, aby wyglądał, jakby zniknął z DOM. Pamiętaj, że te zdarzenia są powiązane z przeciąganymi elementami. Mamy również do czynienia z dodatkowym elementem strony, koszem na śmieci. Ustawimy ten div jako cel przeciągania. Oto kod JavaScript, którego będziemy potrzebować:

var dragTarg = document.querySelector ('. dragTarg');

dragTarg.addEventListener ('dragenter', function (e) {

this.style.border = '3px #aaa przerywana'});

dragTarg.addEventListener ('dragleave', function (e) {

this.style.border = 3px jednolicie czarny ''});

Ta interakcja jest dość prosta. Identyfikujemy nasz element za pomocą selektora zapytania, a następnie dodajemy do niego dwa detektory. Pierwsza określa, co się dzieje, gdy przeciągamy element, dowolny element, na element, a druga określa, co się dzieje, gdy publikowane jest zdarzenie, które mówi, że przeciągany element nie znajduje się już nad naszym elementem. Wszystko, co robimy w okresie pomiędzy tymi wydarzeniami, to zmiana koloru i stylu obramowania wokół zewnętrznej strony div. To daje użytkownikowi wizualną wskazówkę, że przeciąga element na stronie. Zapamiętaj, strona może zawierać inne elementy do przeciągania (takie jak obraz lub link), które po przeciągnięciu nad koszem na śmieci aktywują zdarzenia dragenter i dragleave. Kiedy łączymy ten kod razem, mamy kilka elementów na stronie, które wchodzą ze sobą w interakcje, gdy je przeciągamy. Na przykład, możemy przeciągnąć dowolne kółko dookoła strony, a kiedy przekroczy kosz, kosz zmieni swój stan; a kiedy puścimy przeciągany element, znika on ze strony. Zrobiliśmy to wszystko za pomocą zaledwie kilku wierszy kodu. Kto nie lubi HTML5!

Hack No.48

Zaktualizuj DOM za pomocą obiektu transferu danych typu "przeciągnij i upuść"

Przeciąganie i upuszczanie HTML5 zapewnia prawdziwą relację między przeciągniętym elementem a strefą upuszczania. Ten hack pokazuje, jak przesyłać dane między nimi za pomocą przeciągania danych, a następnie aktualizować DOM na podstawie przesyłanych informacji. Jak można się domyślić, przeciąganie i upuszczanie HTML5 zapewnia znacznie bogatszą interakcję niż to, co było dostępne w przeszłości przy użyciu samego JavaScript. HTML5 publikuje mnóstwo zdarzeń DOM, które mówią nam, co się dzieje po drodze. Wiele razy przeciągasz element, aby przenieść go w inne miejsce na stronie. Wymaga to, aby przeciągany przedmiot i strefa upuszczania mogły przekazywać odpowiednie informacje. Na szczęście HTML5 ma do tego interfejs API! W przypadku tego hacka zaczniemy od czterech elementów na stronie: upuszczanego (do którego chcemy upuścić elementy), który ma wyglądać jak mój kosz na śmieci oraz trzech elementów do przeciągania, które mają wyglądać jak kolorowe kulki. Spójrzmy na znaczniki:

< div class="row" >

< div class="span-6 dragTarg" >< /div >

< /div >

< div class="row" >

< div class="span-6 dragItems" >

< div draggable="true" class="red" >< /div >

< div draggable="true" class="green" >< /div > < div draggable="true" class="blue" >< /div >

< /div >

< /div >

Zauważ, że wszystkie nasze elementy div stylizowane na kolorowe kulki mają atrybut draggable ustawiony na true. Umożliwia to przeciąganie ich po stronie. Zdarzenia odgrywają dużą rolę w tej interakcji (aby uzyskać więcej informacji na temat wydarzeń, które są publikowane podczas przeciągania i upuszczania, przeczytaj "Umożliwianie przeciągania dowolnej zawartości w aplikacji"). Aby uchwycić wydarzenia, które są publikowane podczas przeciągania elementów, dodajemy kilku słuchaczy do strony:

function startDrag(event) {

this.style.border = '5px solid pink';

}

function endDrag(event) {

this.style.border = '';

}

var circles = document.querySelectorAll('.dragItems div');

for(var i=0;i
circles[i].addEventListener('dragstart', startDrag, false);

circles[i].addEventListener('dragend', endDrag, false);

}

Jest to dość prosty skrypt, który dodaje słuchaczy do początku i końca przeciągania każdego elementu. Funkcja startDrag zmienia obramowanie elementu podczas jego przeciągania, a funkcja endDrag zmienia ją z powrotem po zakończeniu. Kontynuujmy i dodajmy kilku słuchaczy do naszego bloku śmieci, który wyznaczyliśmy jako naszą strefę zrzutu:

var dragTarg = document.querySelector('.dragTarg');

dragTarg.addEventListener('dragenter', function(e){

this.style.border = '3px #aaa dashed'});

dragTarg.addEventListener('dragleave', function(e){

this.style.border = '3px solid black'});

Te dwa odbiorniki zostaną dodane do strefy upuszczania. Ponownie zmieniają tylko wygląd przedmiotów (nie dodano jeszcze żadnej dodatkowej funkcjonalności). Strefa upuszczania zmienia się podczas przeciągania (w tym przypadku z ciągłej ramki na przerywaną) i zmienia się z powrotem, gdy element nie jest już nad nią. Kiedy połączymy cały ten kod razem, mamy ładny, ładny obraz scenariusza przeciągania.

Przychodzące: obiekt transferu danych

W tym momencie możesz nie być zadowolony z tego hackowania: prawdopodobnie doszedłeś do wniosku, że możesz dobrze przeciągnąć element, ale ta możliwość nie jest zbyt imponująca, jeśli nie możesz go gdzieś upuścić. Chcemy, aby elementy DIV z kolorowych kulek zmieniały pozycję w DOM, gdy są przeciągane i upuszczane. Aby tak się stało, musimy mieć element do przeciągania, który może przekazywać informacje do strefy upuszczania. Wchodzi obiekt transferu danych. Autorzy specyfikacji HTML5 pomyśleli o wszystkim, nawet o tym, jak przesyłać dane między obiektem przeciągania a strefą upuszczania. Obiekt transferu danych przechowuje dane, które są wysyłane w zdarzeniu przeciągania. Element nasłuchujący przeciągany element ustawia transfer danych w zdarzeniu dragstart; dane są odczytywane w przypadku upuszczenia. Wykonujemy proste wezwanie, aby ustawić dane:

e.dataTransfer.setData (format, data)

Powoduje to ustawienie zawartości obiektu na typ MIME i ładunek danych przekazany jako jego argumenty. W naszym przypadku chcemy, aby te dane identyfikowały informacje o samym elemencie przeciągania, abyśmy mogli przenieść go do DOM, gdy nastąpi faktyczne upuszczenie. Wróćmy do naszego przykładowego kodu i zobaczmy, czy możemy podłączyć to do naszego zdarzenia przeciągania:

function startDrag(event) {

this.style.border = '5px solid pink';

event.dataTransfer.setData("text", this.className);

}

function endDrag(event) {

this.style.border = '';

}

var circles = document.querySelectorAll('.dragItems div');

for(var i=0;i
circles[i].addEventListener('dragstart', startDrag, false);

circles[i].addEventListener('dragend', endDrag, false);

}

Wszystko, co tu naprawdę zrobiliśmy, to dodanie jednej linii kodu. Ustawiamy typ MIME na tekst (ponieważ przekazujemy ciąg znaków), a następnie mamy element przekazujący dane o sobie - w tym przypadku o nazwie klasy. Użyjemy nazwy klasy do identyfikacji obiektu w DOM. Publikowanie danych nie jest zbyt przydatne, jeśli nic ich nie odczytuje. Więc naszym następnym krokiem jest przeniesienie zawartości po wykonaniu upuszczenia. W tym celu skonfigurujemy detektor zdarzenia drop. Ten słuchacz trafia do strefy upuszczania. Mamy już zdarzenie dragenter i dragleave w naszej strefie upuszczania, ale potrzebujemy słuchacza, aby dołączyć do zdarzenia drop. Oto nasz dodatkowy kod:

function dropit(event){

event.preventDefault()

var myElement = document.querySelector('.dragItemsB .'

+event.dataTransfer.getData('text');

this.appendChild(myElement), false);

};

dragTarg.addEventListener('dragover', function(e){

e.preventDefault();

});

dragTarg.addEventListener('drop', dropit, false);

Być może zauważyłeś, że obsłużyliśmy dwóch różnych słuchaczy. Najpierw dodaliśmy detektor dragover, którego jedynym celem jest zapobieganie domyślnej akcji dragover. Jest to konieczne, aby ujawnić zdarzenie upuszczania, które ponownie jest jedynym miejscem, w którym możesz mieć dostęp do obiektu przesyłania danych, który opublikowałeś w swoim dragstart.

Funkcjonalność jest dość prosta. Używamy nazwy klasy, którą wyciągnęliśmy z obiektu transferu danych, aby znaleźć nasz element do przeciągania w DOM. Następnie wykonujemy prostą metodę appendChild, aby umieścić ten element w strefie upuszczania. Gdy znajdzie się w strefie zrzutu, nasz CSS włącza się, aby zamienić tę kolorową piłkę w kolorową płaską linię. Po upadku strefa zrzutu.

Atrybut strefy zrzutu

Oto dodatkowa ciekawostka na temat przeciągania i upuszczania. Chociaż może to być dla użytkowników przyjemny i intuicyjny sposób pracy z aplikacją, wielu użytkowników może uzyskiwać dostęp do witryny w inny sposób, na przykład za pomocą czytnika ekranu. HTML5 dodał atrybut, który zapewni dodatkową przejrzystość tym użytkownikom. Dodano atrybut dropzone, aby zidentyfikować obszary dokumentu, w których można upuszczać towary. Pomaga to nietradycyjnym interfejsom zrozumieć, gdzie można umieścić elementy na stronie. Zaktualizujmy naszą strefę zrzutu, aby z niej skorzystać

< div class="row" >

< div class="span-6 dragTarg" dropzone="true" >< /div >

< /div >

Nie ekscytuj się zbytnio - dodanie tego atrybutu w żadnym wypadku nie sprawia, że ten element div jest bardziej strefą upuszczania niż był. Wciąż potrzebujemy do tego naszych słuchaczy. Ten atrybut dostarcza raczej dodatkowych informacji i sprawia, że nasza strona jest znacznie bardziej dostępna.

Hack No.49

Przeciągaj pliki do i z aplikacji internetowej

Nie jesteś już ograniczony ograniczeniami okna przeglądarki. Przeciąganie i upuszczanie HTML5 może przenosić pliki z chmury na komputer, a pliki z komputera do chmury - wszystko za pomocą zaledwie kilku wierszy JavaScript. HTML5 ma moc przetwarzania danych bezpośrednio w przeglądarce, bez wchodzenia na serwer. Od tekstu po obrazy i wideo - HTML5 ma moc. Oprócz tego funkcja przeciągania i upuszczania HTML5 zapewnia nam łatwy interfejs do pobierania danych do i z przeglądarki. Po prostu pobieramy dane z naszego systemu operacyjnego i umieszczamy je w naszej przeglądarce. W tym hacku będziemy przesyłać pliki do systemu operacyjnego z naszej przeglądarki, a następnie z powrotem do naszej przeglądarki z systemu operacyjnego. Zacznijmy od przeciągnięcia danych do naszego systemu operacyjnego z przeglądarki.

Przywracanie plików do domu

Możliwość przeciągania treści z przeglądarki do naszego systemu operacyjnego zaczyna się od pojedynczego elementu w naszej aplikacji. Każdy plik, który chcemy ściągnąć z chmury, musi być powiązany z elementem w takiej czy innej formie. W przypadku tego hacka zaczniemy od trzech elementów div na naszej stronie, wszystkich stylizowanych na kolorowe kulki. Każdy element zostanie powiązany z plikiem tekstowym w chmurze. Spójrzmy na nasze znaczniki:

< div class = "span-6 dragItemsC" >

< div class = "red" draggable = "true" data-downloadurl = "application / octet-stream: colorRed.txt: to jest kolor czerwony " >

< /div >

< div class = "green" draggable = "true" data-downloadurl = "aplikacja / strumień oktetu: colorGreen.txt: http://chapter6.boyofgreen.c9.io/assets/test.txt " >

< /div >

< div class = "blue" draggable = "true" data-downloadurl = "application / octet-stream: test.txt: http: //thecssninja.com/gmail_dragout/Eadui.ttf " >

< /div >

< /div >

Znaczniki są dość proste i renderowane w naszej przeglądarce.

Przyjrzyjmy się nieco naszym atrybutom. Pierwszym atrybutem każdego elementu div jest atrybut draggable, ustawiony na true. Jest to podstawowy atrybut funkcji przeciągnij i upuść, ponieważ umożliwia nam wybranie elementu ze strony i przeciągnięcie go. Niektóre elementy, takie jak obrazy i href, są domyślnie przeciągane. W rzeczywistości, jeśli przeciągniemy obraz lub href do systemu operacyjnego, pojawi się plik. W przypadku obrazu kopiowałby obraz z chmury, a dla href stanowiłby skrót do strony internetowej, na którą wskazywał tag linku. W naszym przypadku używamy elementów div, więc atrybut jest konieczny. Drugi atrybut, który widzimy, to data-downloadurl, a każdy z tych atrybutów wskazuje na adres URL pliku, który chcemy pobrać z chmury. Jeśli przeczytałeś niektóre z wcześniejszych hacków, możesz rozpoznać format "data-" jako niestandardowy atrybut danych. To kolejna funkcja HTML5, która pozwala nam dodawać dodatkowe dane do dowolnego elementu w naszym DOM. W tym przypadku używamy go do przechowywania naszego adresu URL. Gdybyś załadował tę stronę teraz, byłbyś w stanie przeciągnąć te elementy div po stronie, ale gdybyś spróbował przeciągnąć je na pulpit, nic by się nie stało. Aby umożliwić przesyłanie plików danych, musimy dodać kilka słuchaczy typu "przeciągnij i upuść". Dodamy do naszej strony kilka wierszy kodu JavaScript, który dodaje detektory do każdego elementu div. Słuchacze uruchomią prostą funkcjonalność, aby pomóc naszemu dropowi wiedzieć, co upuścić.

function startDrag (event) {

event.dataTransfer.setData ("DownloadURL", this.getAttribute ("data-downloadurl"))

};

var circle = document.querySelectorAll ('. dragItems div');

for (var i = 0; i
circle [i] .addEventListener ('dragstart', startDrag, false);

};

Przejdźmy przez to, co się tutaj dzieje. Słuchacze przyłączają się do zdarzenia dragstart. Zdarzenie dragstart jest ważne w tym procesie, ponieważ jest to jedyny punkt przeciągania i upuszczania, w którym można dodać dane do obiektu dataTransfer. (Aby uzyskać więcej informacji na temat obiektu przesyłania danych, zobacz [Hack # 56].) W tym przypadku dołączamy detektor, który odpala funkcję startDrag. Ta funkcja zawiera tylko jeden wiersz kodu, który pobiera ciąg data-downloadurl z każdego elementu i dodaje go do atrybutu obiektu dataTransfer o nazwie DownloadURL. Aby to osiągnąć, używamy prostej metody setData. Gdy mamy już tę wartość w obiekcie dataTransfer, przejmuje ją nasza przeglądarka obsługująca HTML5. Zobaczmy teraz, co się stanie, gdy przeciągniemy jeden z tych elementów poza okno przeglądarki. Zamiast otrzymywać symbol "niedozwolone" podczas przeciągania, otrzymujemy symbol wskaźnika, który mówi nam, że wszystko jest w porządku w świecie przeciągania i upuszczania. Jeszcze lepiej, kiedy już upuścisz element poza okno przeglądarki, widzimy, że plik tekstowy z chmury pojawia się w naszym systemie plików OS (w tym przypadku na pulpicie). To magia przeciągania i upuszczania!

Przywracanie plików z powrotem do przeglądarki

Wow, teraz, gdy mamy możliwość przenoszenia plików z chmury do naszego systemu operacyjnego, wykorzystajmy część tej mocy HTML5, aby przenieść do przeglądarki pliki, które zapisaliśmy lokalnie. Należy pamiętać, że następnym krokiem nie jest przeniesienie plików do chmury; wymagałoby to innego hackowania. To, co tutaj robimy, to przenoszenie plików z systemu operacyjnego do przeglądarki, abyśmy mogli manipulować tymi danymi. Zaczniemy od pliku tekstowego o nazwie coolKid.txt, który znajduje się na pulpicie. W tym pliku ext jest prosta linia, która mówi: "Jeff to naprawdę fajny dzieciak"; w tym momencie chcesz umieścić ten tekst w polu tekstowym w naszej aplikacji, abyśmy mogli pracować z tekstem w przeglądarce. Tym razem znaczniki, od których zaczniemy, są bardzo proste. To jest obszar tekstowy:

< div class = "row" >

< textarea id = "showDrop" > < /textarea >

< /div >

Jedynym atrybutem w tym obszarze tekstowym jest id, a zgadłeś, dodamy do niego słuchaczy. Przejdźmy od razu do JavaScript, w którym dodajemy słuchaczy:

var showDrop = document.getElementById ('showDrop');

showDrop.addEventListener ('dragover', function (e) {

e.preventDefault ()

});

showDrop.addEventListener ('drop', readData, false);

Gdybyśmy nie dodali JavaScript do tej strony i przeciągnęlibyśmy nasz plik tekstowy na nasz obszar tekstowy, przeglądarka potraktuje przeciągnięty plik jako hiperłącze i spróbuje załadować go w oknie przeglądarki. W naszym przypadku opuścilibyśmy aplikację i załadowalibyśmy naszą linię tekstu, która mówi: "Jeff to naprawdę fajny dzieciak". Jak widać w poprzednim kodzie, dodajemy dwa detektory do tego elementu. Pierwszy to nasłuchiwanie przeciągania, które istnieje tylko z jednego powodu: aby zapobiec wystąpieniu domyślnej akcji. Bez detektora zdarzenie drop nigdy nie zostanie uruchomione. Następny detektor wywołuje funkcję, która zajmie się przeniesieniem danych do naszej aplikacji HTML5:

var readData = function (e) {

e.stopPropagation (); // Powstrzymuje niektóre przeglądarki przed przekierowaniem.

e.preventDefault ();

var filelist = e.dataTransfer.files;

if (! filelist) {return}

var filelist = event.dataTransfer.files;

if (filelist.length> 0) {

var plik = filelist [0];

var filereader = nowy FileReader ();

filereader.myTarg = document.getElementById ('showDrop');

var myData = function (event) {

this.myTarg.value = this.result

};

filereader.onloadend = myData;

filereader.readAsText (file);

};

};

Ta funkcja wydaje się stosunkowo złożona w porównaniu z jedną linijką kodu, która wymagała przeniesienia pliku na nasz pulpit, więc przejdźmy przez to, co się dzieje. Pierwszą rzeczą, którą musimy zrobić, jest powstrzymanie przeglądarki przed próbą załadowania pliku w oknie przeglądarki. Mamy do tego dwie metody, ponieważ niektóre przeglądarki wymagają obu:

e.stopPropagation (); // Powstrzymuje niektóre przeglądarki przed przekierowaniem.

e.preventDefault ();

Następnie zbudujemy wskaźnik (użyjemy tego odniesienia kilka razy) do naszego obiektu dataTransfer, który prawdopodobnie będziesz pamiętać, kiedy sprowadziłeś plik do systemu operacyjnego. W tym obiekcie powinna znajdować się tablica o nazwie files, która będzie zawierała listę wszystkich plików (tak, możemy mieć więcej niż jeden), które są przeciągane do tego elementu:

var filelist = e.dataTransfer.files;

Nie wszystko, co jest przeciągane i upuszczane w oknie, jest plikiem, więc następnym krokiem jest sprawdzenie, czy przesyłamy plik; jeśli tak, długość tej tablicy będzie wynosić co najmniej 1. Owijamy resztę naszego kodu w tym warunku if, ponieważ jeśli nie ma pliku w przeciągnięciu, nie pozostaje nic innego do zrobienia:

if (filelist.length> 0)

.....

}

W następnym kroku wywołamy metodę, która pomoże nam odczytać plik i wydobyć z niego potrzebne nam dane. Nazywa się to metodą FileReader, więc wywołamy ją jako zmienną o nazwie filereader:

var filereader = new FileReader ();

Kilka następnych wierszy kodu dotyczy czytnika plików. Pierwszą rzeczą, którą zrobimy, jest zbudowanie odniesienia do miejsca, w którym chcemy, aby tekst pojawił się - w tym przypadku naszego obszaru tekstowego - w obiekcie filereader. Ułatwi to późniejsze odniesienie.

filereader.myTarg = document.getElementById ('showDrop');

To jest prawdopodobnie najtrudniejsza część: zamierzamy ustawić zamknięcie, które jest w zasadzie częściowo wykonaną funkcją, której będziemy używać po pełnym załadowaniu pliku w przeglądarce. Ta metoda przyjmuje inną wartość, którą dodamy do obiektu filereader, zwaną wynikami. Wartość wyniku będzie w rzeczywistości tekstem z naszego pliku, który za chwilę wyciągniemy.

var myData = function (event) {this.myTarg.value = this.result};

Ostatnie dwa wiersze kodu uruchamiają zamknięcie o nazwie myData w zdarzeniu loadEnd, a następnie uruchamiają metodę readAsText, która ma odniesienie do rzeczywistego pliku w środku:

filereader.onloadend = myData;

filereader.readAsText (filelist [0]); Istnieją nieskończone możliwości, co można zrobić z plikami lokalnymi po umieszczeniu ich w przeglądarce. W takim przypadku mogę przesłać te dane na serwer lub mogę je jeszcze bardziej edytować i ponownie przeciągnąć lokalnie. HTML5 przenosi funkcjonalność, która wcześniej była ograniczona do serwera, bezpośrednio do Twojej przeglądarki.

Hack No.50

Dowolny element na swojej stronie może być dostosowywany przez użytkownika dzięki zawartości do edycji

Wprowadzane dane przez użytkownika były kiedyś ograniczone do elementów formularza, takich jak dane wejściowe i obszary tekstowe, ale dzięki funkcji Edytowalna zawartość HTML5 każdy element strony może stać się edytowalny. Użyj tej funkcji, aby umożliwić użytkownikom dostosowywanie swoich stron. HTML5 otwiera drzwi do ostatecznego dostosowania dla naszych użytkowników. Jedną z nowych funkcji, która to szczególnie ułatwia, jest zawartość edytowalna. Może to być również jedna z najprostszych funkcji do zaimplementowania. W tym hacku chcemy, aby nasi użytkownicy mogli dostosowywać zawartość swoich stron - spersonalizować ich stronę, jeśli chcesz. Aby było to możliwe, udostępnimy główną zawartość strony do edycji. Spójrzmy na nasze znaczniki:

< div contenteditable = "true" class = "row" id = "editable" >

< p >

To naprawdę świetna książka.

Tak się cieszę, że to czytam, ponieważ:

< /p >

< ul >

< li > to jest dowcipne < /li >

< li > Jestem teraz dobrze poinformowany o HTML5 < /li >

< li > autorzy to świetni faceci < /li >

< /ul >

< /div >

Być może zauważyłeś nowy atrybut w naszym module div kontenera, który mówi, że contenteditable = "true"; ten atrybut jest wszystkim, czego potrzebujemy, aby nasze treści były dostosowywane przez użytkowników. Tak, jakby pisali w polu tekstowym, nasi użytkownicy mogą teraz edytować tę sekcję strony internetowej bezpośrednio w przeglądarce, bez konieczności stosowania dodatkowych narzędzi. HTML jest na tyle inteligentny, że wie, jaką część znaczników edytuje i dopasowuje nową zawartość do elementów, w których się znajdują. Na przykład, jeśli użytkownik znajduje się na nieuporządkowanej liście (ul) i naciśnie klawisz Return, doda kolejną li do listy. Naciśnięcie klawisza Return w akapicie tworzy nowy akapit i tak dalej. Teraz, w tym momencie, jeśli użytkownik odświeży stronę, cała zaktualizowana zawartość zniknie i zostanie ponownie załadowana z serwera. Należy zauważyć, że zawartość edytowalna nie jest tylnymi drzwiami do aktualizacji zawartości na serwerze. Nie chcemy aktualizować danych w chmurze, ale chcemy, aby pozostały one dla tego użytkownika, więc przejdziemy do innego narzędzia z zestawu narzędzi HTML5, zwanego pamięcią lokalną. Pamięć lokalna umożliwia nam zapisywanie danych bezpośrednio w przeglądarce. W przeciwieństwie do plików cookie, które z czasem są usuwane, lokalna pamięć jest przechowywana do momentu, gdy Ty (twórca aplikacji) lub użytkownik celowo ją usuniesz. Przeniesiemy zawartość zaktualizowaną przez użytkownika w tym edytowalnym obszarze i zapiszemy ją w pamięci lokalnej, abyśmy mogli jej ponownie użyć. Aby to osiągnąć, wyciągniemy kilka wierszy JavaScript:

var myEdit = document.getElementById('editable');

var setEditMemory = function(content){

localStorage.setItem("myContent", myEdit.innerHTML);

};

myEdit.addEventListener('blur',setEditMemory);

Zasadniczo dodajemy odbiornik do zdarzenia rozmycia naszego edytowalnego elementu div. Zwykle elementy div nie mają zdarzeń rozmycia (ponieważ nie mogą one naturalnie skupiać się na fokusie), ale ponieważ udostępniliśmy nasz element div do edycji, automatycznie przejmuje fokus i dlatego po wyjściu z edytowalnej zawartości jest uruchamiane zdarzenie rozmycia. Ten odbiornik po prostu pobiera zawartość z div i przechowuje ją jako ciąg w pamięci lokalnej pod nazwą wartości myContent. Pomyślnie zapisaliśmy zawartość; teraz musimy go tylko użyć do zaktualizowania zawartości strony, gdy użytkownik ponownie odwiedzi naszą stronę. W tym celu dodamy kilka dodatkowych wierszy JavaScript podczas ładowania strony:

if(localStorage.getItem("myContent")){

myEdit.innerHTML = localStorage.getItem("myContent");

}

Po prostu sprawdzamy, czy nasza wartość myContent została utworzona. Jeśli tak, wiemy, że użytkownik odwiedził wcześniej tę stronę i mógł zaktualizować tę zawartość. Więc pobieramy wartość myContent z lokalnej pamięci i aktualizujemy nasz edytowalny div z zawartością. Teraz użytkownik zobaczy zaktualizowaną zawartość za każdym razem, gdy wróci na tę stronę. Magiczny!

Czarujące!

Kolejną fajną funkcją, która jest dostępna w edytowalnej treści, jest sprawdzanie pisowni. Zgadza się, wbudowane sprawdzanie pisowni. Podobnie jak w edytorze tekstu na komputerze, jeśli błędnie wpiszesz słowo w HTML5, pojawi się pod nim falista linia, a jeśli klikniesz to słowo prawym przyciskiem myszy, możesz wybrać słowo zastępcze z wbudowanego słownika przeglądarki. Jeśli z jakiegoś powodu zdecydujesz, że nie chcesz tej funkcji w swoim dokumencie, możesz ją po prostu wyłączyć, ustawiając atrybut sprawdzania pisowni na fałsz: < div class="row" id="editable" contentEditable="true" spellcheck="false" >...< /div >

HTML5 dał nam większość edytora tekstu bezpośrednio w naszej przeglądarce! Pomyśl tylko o możliwościach.

Hack No.51

Zmień swoją stronę internetową w edytor WYSIWYG

Ułatw aktualizowanie zawartości strony internetowej bez opuszczania przeglądarki. Kilka prostych linii kodu może zmienić twoją stronę internetową w edytor stron internetowych. Kiedyś redaktorzy WYSIWYG byli wściekli. Niestety, przejście od tego, co widziałeś w swoim edytorze do tego, co widziałeś w przeglądarce, było zawsze trudne. HTML5 zapewnia klucz do działania.

Udostępnij stronę do edycji

Pierwszym krokiem do przekształcenia strony internetowej w edytor stron internetowych jest umożliwienie edycji całej zawartości strony internetowej. Podczas poprzednich hacków rozmawialiśmy o tym, co jest potrzebne, aby udostępnić sekcję naszej strony do edycji, i równie łatwo jest udostępnić do edycji całą naszą stronę. Zajmuje tylko jedną linię kodu:

document.designMode = "on"

Aby więc nieco kontrolować tę funkcję, zaczniemy od strony internetowej z fantastyczną zawartością. Użyjemy dodanych elementów sterujących, aby włączyć i wyłączyć tryb projektowania naszego dokumentu oraz wyeksportować kod. Spójrzmy na znaczniki naszych elementów sterujących strony:

< div class="row" >

< p >

use this button to make your entire document editable or turn it off:

< /p >

< p >< button class="btn" id="makeEdit" >toggle design mode< /buton >< /p >

< p >use this button to show the markup< /p >

< p >< button class="btn" id="showMarkup" >show my markup< /buton >< /p >

< p >< textarea id="exportContent" >< /textarea >< /p >

< /div >

Zasadniczo mamy tekst, dwa przyciski i obszar tekstowy. Przyciski będą wymagały słuchaczy, aby mieć funkcjonalność. Spójrzmy na JavaScript, który mamy dla tej aplikacji:

var button = document.getElementById ('makeEdit');

button.addEventListener ('click', function (e) {

if (document.designMode === "off") {

document.designMode = "on"

}else{

document.designMode = "off"

}

});

var showMarkup = document.getElementById ('showMarkup');

showMarkup.addEventListener ('click', function (e) {

var str = '< HTML >' + document.documentElement.innerHTML + '< /HTML >'

document.getElementById ('exportContent'). value = str;

});

Pierwszy przycisk służy do przełączania widoku projektu. Mamy detektor dołączony do zdarzenia kliknięcia przycisku, który wykonuje prosty JavaScript. Sprawdza, czy tryb projektowania jest wyłączony, a jeśli tak, włącza go. Jeśli tryb projektowania jest włączony, wyłącza go. Drugi przycisk ma również detektor, który uruchamia funkcjonalność eksportu zawartości strony. Logika jest dość prosta. W dowolnym momencie można kliknąć ten przycisk, a my uruchomimy JavaScript, który skopiuje całą zawartość (HTML, wszelkie skrypty wbudowane itp.) Z elementu documentElement. To daje nam całą zawartość wewnątrz znaczników HTML jako ciąg. Pobierany jest ciąg treści, tagi HTML i znacznik (ponieważ znajduje się poza treścią) są ponownie dodawane do ciągu, a następnie ciąg jest ustawiany jako wartość obszaru tekstowego. Używamy obszaru tekstowego, aby ciąg pozostał jako tekst, a przeglądarka nie próbuje go wykonać. W tym momencie mamy cały znacznik strony w miejscu, w którym można go skopiować i ponownie wykorzystać. Możemy teraz dokonywać zmian bezpośrednio na naszej stronie internetowej. Pamiętaj, że edytujemy tekst na stronie; nie możemy zmieniać rozmiaru ani położenia elementów. Ale nie musimy się martwić, że nasza strona nie będzie renderowana tak samo, jak wygląda w naszym edytorze, ponieważ nasza wyrenderowana strona stała się naszym edytorem!

Hack No.52

Przejmij kontrolę nad przyciskami historii przeglądarki dzięki historii sesji HTML5

HTML5 zapewnia elegancki sposób programowego sterowania stosem historii w aplikacji internetowej. Ile razy byłeś w świetnej aplikacji internetowej, tylko po to, by kliknąć przycisk Wstecz w przeglądarce, myśląc, że cofnie ostatnią nawigację, ale zamiast tego przeniesie Cię z powrotem do ostatniej witryny? Historia sesji HTML5 zapewnia łatwy interfejs do zarządzania historią w aplikacji. W przeszłości programiści robili trudne rzeczy z aplikacjami, aby im to umożliwić i "oszukać" zachowanie historii przeglądarki, używając znaczników hash w adresie URL. Zwykle programista dodaje coś w rodzaju "#mynewurl" na końcu adresu URL swojej strony i dodaje to nową pozycję na stosie historii. Za każdym razem, gdy dodajesz "#" do adresu URL, jest on traktowany jako pozycja na stronie i nie opuszcza strony po kliknięciu przycisku Wstecz. Historia sesji umożliwia zaprogramowanie tego zachowania bezpośrednio w aplikacji. Możesz nawet zaktualizować adres URL swojej strony bez odświeżania strony (jednak zachowaj ostrożność; nie chcesz tworzyć adresów URL, których serwer nie może rozwiązać, na wypadek, gdyby użytkownik dodał je do zakładek lub próbował wrócić później czas). Dla tego hacka mamy tag < canvas >, na którym zbudujemy uśmiechniętą buźkę. Wykonamy każdy krok procesu kompilacji, klikając przycisk Wstecz w przeglądarce.

Uśmiechnij się, to historia!

Zaczniemy od strony, która nie ma nagłówka i zawiera tylko trochę treści. Zwykle kliknięcie przycisku Wstecz w tym momencie spowoduje powrót do strony, na której byłeś przed odwiedzeniem tej witryny. Więc dodamy kilka nowych wpisów do naszego stosu historii, abyśmy mogli zacząć:

window.history.pushState ('leftEye', 'makeLeftEye');

window.history.pushState ('rightEye', 'makeRightEye');

window.history.pushState ('mouth', 'makeMouth');

window.history.pushState ('face', 'makeface');

window.history.pushState ('ready', 'letsgo');

Tutaj wprowadziliśmy pięć nowych wpisów do naszego stosu historii. W tym momencie, gdybyśmy odświeżyli naszą stronę, moglibyśmy kliknąć przycisk Wstecz pięć razy, zanim przeniesie nas z powrotem do poprzedniej witryny. Jednak nic się nie zmieni na stronie po kliknięciu przycisku Wstecz. W tym celu musimy ustawić słuchacza:

window.addEventListener ("popstate", drawStack, false);

Ten detektor będzie uruchamiał funkcję drawStack po każdym kliknięciu przycisku Wstecz. Ponieważ jest to zdarzenie, po prostu automatycznie przekaże obiekt zdarzenia jako zdarzenie kliknięcia:

var drawStack = function(){

switch(window.history.state)

{

case 'leftEye':

makeLeftEye();

break;

case 'rightEye':

makeRightEye();

break;

case 'mouth':

makeMouth();

break;

case 'face':

makeFace();

break;

default: break

}

};

Wewnątrz tej metody faktycznie widzimy instrukcję switch. Ta instrukcja switch służy do określenia, który krok naszego procesu jest wywoływany za każdym razem, gdy kliknięty zostanie przycisk Wstecz, oraz jaka metoda zostanie uruchomiona w celu obsługi funkcji. Jeśli spojrzysz wstecz, kiedy dodaliśmy wpisy do stosu historii, zobaczysz, że każdy z nich wszedł z nazwą wartości i wartością. Ta instrukcja switch sprawdza nazwę wartości, aby określić, w której pozycji stosu się znajdujemy. Każdy przypadek w tej instrukcji uruchamia jedną z następujących funkcji:

var mySmile = document.getElementById('mySmile2')

var smileCtx = mySmile.getContext('2d');

var makeFace = function(){



smileCtx.beginPath();

smileCtx.fillStyle = '#F1F42E';

smileCtx.arc(100,100,99,0,Math.PI*2); // head

smileCtx.stroke();

smileCtx.fill();

};

var makeMouth = function(){

smileCtx.beginPath();

smileCtx.moveTo(170,100);

smileCtx.arc(100,100,70,0,Math.PI); // Mouth

smileCtx.stroke();

};

var makeLeftEye = function(){

smileCtx.beginPath();

smileCtx.fillStyle = 'black';

smileCtx.moveTo(60, 65);

smileCtx.arc(60,65,12,0,Math.PI*2); // Left eye

smileCtx.fill();

};

var makeRightEye = function (){

smileCtx.beginPath();

smileCtx.fillStyle = 'black';

smileCtx.moveTo(140,65);

smileCtx.arc(140,65,12,0,Math.PI*2); // Right eye

smileCtx.fill();

};

Każda faza rysuje coś nowego. Jeśli ponownie odświeżymy stronę i klikniemy przycisk Wstecz, zobaczymy teraz żółte kółko na stronie. Jeśli nadal będziemy klikać przycisk Wstecz, zostanie narysowany uśmiech, następnie prawe oko, a następnie lewe oko. Po czterokrotnym kliknięciu przycisku Wstecz będziemy mieli pełną uśmiechniętą buźkę.

Jeśli masz bystre oko, być może zauważyłeś, że czterokrotnie kliknęliśmy przycisk Wstecz, ale umieściliśmy pięć wpisów w stosie historii. Możesz pomyśleć, że nadal masz jeden dodatkowy przycisk Wstecz, klikając tę stronę, zanim wrócisz do poprzedniej strony. Mylisz się. Dodatkowy wpis faktycznie obsługuje początkowe ładowanie strony. Zdarzenie uruchamiane dla stanu popstate ma miejsce przy zmianie historii, a nie po kliknięciu przycisku wstecz, więc w rzeczywistości jest uruchamiane po załadowaniu strony, ponieważ ładowanie strony dodaje się do historii.

Inne funkcje historii

Historia sesji ma dużo więcej do zaoferowania niż tylko pojedyncze wydarzenie popowe. Oto kilka podstawowych metod, które można wywołać na stosie historii strony:

window.history.length

Liczba wpisów w historii sesji.

window.history.state

Aktualny stan obiektu.

window.history.go (n)

Przechodzi do tyłu lub do przodu o określoną liczbę kroków. Jeśli podana wartość wynosi 0, załaduje ponownie bieżącą stronę.

window.history.back ()

Cofa się o jeden krok.

window.history.forward ()

Przechodzi do przodu o jeden krok.

window.history.pushState (dane, tytuł [, url])

Wypycha dane określone w argumentach do historii sesji.

window.history.replaceState (dane, tytuł [, adres URL])

Aktualizuje bieżący wpis w historii sesji.

Hack No.53

Osadź dane binarne we wbudowanym adresie URL

Adresy URL danych stanowią alternatywę dla odwoływania się do zewnętrznych zasobów za pomocą tagów obrazu i linków. Najbardziej typowy scenariusz obejmuje obrazy, do których można odwoływać się bezpośrednio z dokumentu HTML lub pojedynczego arkusza stylów. Wcześniej zbadaliśmy kompromisy wydajnościowe między sprite′ami CSS i wykorzystaniem identyfikatorów URI danych w zewnętrznych arkuszach stylów. W tym hacku skupimy się na usuwaniu danych obrazu w naszych znacznikach HTML, a nawet zobaczymy kilka różnych sposobów, aby to zrobić, używając różnych implementacji szablonów po stronie serwera. Adresy URL danych są podtypem schematu Uniform Resource Identifier (URI), który osadza dane zasobów w adresie URL jako ciąg zakodowany algorytmem Base64. W przeciwieństwie do tradycyjnych adresów URL wskazujących na zasoby zewnętrzne, takie jak obrazy, arkusze stylów i kod JavaScript, przeglądarka nie wysyła żądania HTTP do zawartości zdalnej. Czasami możemy poprawić wydajność aplikacji internetowych, wykorzystując technikę - na przykład w środowisku, w którym przepustowość połączenia może być ograniczona, na przykład połączenie internetowe na pokładzie amerykańskiego okrętu wojennego. W mobilnych aplikacjach internetowych często korzystne jest również zmniejszenie liczby żądań HTTP poprzez osadzanie mniejszych obrazów na stronie. Ponadto przypadki, w których możesz chcieć dynamicznie generować obrazy po stronie serwera na podstawie unikalnego profilu, pory dnia lub lokalizacji odwiedzającego witrynę, mogą również uzasadniać osadzony obraz. Adresy URL danych mają następującą składnię:

data:[mimetype][;base64],[data]

W tagu Images

W przypadku tego najpierw skorzystamy z usługi online, ręcznie przesyłając obraz do dataurl.net. Po skopiowaniu ciągu danych zakodowanego w Base64 z obszaru tekstowego po lewej stronie, możemy wkleić go do naszego dokumentu (poniższy kod jest skrócony dla zwięzłości, ale pamiętaj, że im większy obraz, tym dłuższy ciąg danych będzie):

< img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD//gA ... / >

W zewnętrznym arkuszu stylów

Teraz użyjemy składni url () w selektorze CSS, podobnie jak w przypadku wywołania zewnętrznego obrazu tła:

#backg {

height: 326px;

background-image:

url("data:image/jpeg;base64,/9j/4SJnRXhpZgAATU0AKgAAAAgADAEAAAMA ...

}

Nie zapomnij odpowiedniego znacznika:

< div id = "backg" >

Uzyskiwanie pomocy w ramach struktury aplikacji sieci Web

Często nie jest możliwe ręczne przesłanie naszych obrazów do dataurl.net, więc w środowiskach produkcyjnych możemy to zrobić programowo w ramach naszej aplikacji internetowej na serwerze. Oto kilka najpopularniejszych narzędzi do tego:

Grails

Za pomocą wtyczki Grails Rendering:

class SomeController {

def generate = {

def file = new File("path/to/image.png")

renderPng(template: "thing", model: [imageBytes: file.bytes])

}

}

W widoku:

< html >

< head >< /head >

< body >

< p >Below is an inline image< /p >

< rendering:inlinePng bytes="${imageBytes}" class="some-class" / >

< /body >

< /html >

Node.js

Konfigurując trasę i tworząc bufor, z drugim argumentem jako binarnym, po którym następuje konwersja ciągu zakodowanego w Base64:

express = require("express")

request = require("request")

BufferList = require("bufferlist").BufferList

app = express.createServer(express.logger(), express.bodyParser())

app.get "/", (req, res) ->

if req.param("url")

url = unescape(req.param("url"))

request

uri: url

encoding: 'binary'

, (error, response, body) ->

if not error and response.statusCode is 200

data_uri_prefix = "data:" + response.headers["content-type"]

+ ";base64,"

image = new Buffer(body.toString(),

"binary").toString("base64")

image = data_uri_prefix + image

res.send "< img src=\"" + image + "\"/ >"

app.listen 3000

Ruby On Rails (przez Asset Pipeline)

Korzystając z pomocnika asset_data_uri:

#logo { background: url(<%= asset_data_uri 'logo.png' %>) }

Django

Używając prostego filtru Django, takiego jak ten wspomniany w djangosnippets.org:

from django import template

from base64 import b64encode

register = template.Library()

@register.filter

def dataURI(filename, mime = None):

"""

Ten filtr zwróci URI danych dla podanego pliku, więcej informacji znajdziesz w: http://en.wikipedia.org/wiki/Data_URI_scheme

Przykładowe użycie:

< img src="{{ "/home/visgean/index.png"|dataURI }}" >

zostanie przefiltrowany na :

< img src="data:image/png;base64,iVBORw0..." >

"""

with open(filename, "rb") as file:

data = file.read()

encoded = b64encode(data)

mime = mime + ";" if mime else ";"

return "data:%sbase64,%s" % (mime, encoded)

Wady korzystania z adresów URL danych

Korzystanie z adresów URL danych ma pewne wady, a mianowicie:

Buforowanie

Przeglądarka nie będzie buforować wbudowanych obrazów, które używają adresów URL danych do przechowywania danych zakodowanych w Base64. Jeśli obraz jest używany więcej niż raz w aplikacji, użycie adresu URL danych może nie być optymalne. Będziesz musiał oprzeć optymalną równowagę na zachowaniu użytkowników i ruchu na stronach, na których obraz się powtarza.

Rozmiar pliku

Obraz zakodowany w Base64 jest o jedną trzecią większy niż równoważny obraz binarny. Ponownie, będziesz musiał zdecydować o tej równowadze w oparciu o dane zachowanie użytkowników i wzorce ruchu.

Hack No.54

Przekonwertuj identyfikator URI danych na obiekt BLOB i dołącz go do danych formularza za pomocą XHR2

HTML5 zmienił przeglądarkę internetową w pełnoprawne środowisko wykonawcze. Przy całej tej nowej funkcjonalności klienta potrzebne są sposoby bezpiecznego i programowego przesyłania danych z powrotem na serwer. Ten hack pokazuje, jak to zrobić. Wyobraź sobie, że tworzysz interfejs w witrynie mediów społecznościowych, który umożliwia użytkownikom tworzenie motywów strony profilu z formularza, który dynamicznie generuje style. Chcesz zachować te style w bazie danych na serwerze, przesyłając obrazy w tle za pośrednictwem XMLHttpRequest i ładując je na dodatkowe żądania stron. Aby to zrobić, możesz skorzystać z XHR2 i nowej funkcji, która umożliwia przesyłanie obiektów blob i plików przez dołączenie ich do obiektu FormData. Zanim przejdziemy do szczegółów FormData i XHR2, stwórzmy prosty obiekt Blob. Najpierw wywołamy new w klasie Blob, a następnie przekażemy dane do Bloba. A może prosty styl, w którym nasz tekst jest czerwony?

var stylesblob = new Blob (['body {color: red;}'], {type: 'text / css'});

Teraz utworzymy nowy link, ustawimy kilka atrybutów i dołączymy go gdzieś w dokumencie:

var stylesblob = new Blob(['body{color:red;}'], {type:'text/css'});

Teraz utworzymy nowy link, ustawimy kilka atrybutów i dołączymy go gdzieś w dokumencie:

var link = document.createElement('link');

link.rel = 'stylesheet';

link.href = window.URL.createObjectURL(stylesblob);

document.body.appendChild(link);

To jest prosta demonstracja narzędzia Blob, ale co, jeśli musimy dołączyć te dane do formularza i przesłać je na zdalny serwer? Najpierw musimy zająć się formularzem:

< form enctype="multipart/form-data" method="post" name="profileStyle" >

< label >username:< /label >

< input type="email" autocomplete="on" autofocus name="userid" placeholder="email" required/ >

< label >Styles to save!< /label >

< input type="file" name="file" required / >

< /form >

Należy tu zwrócić uwagę na kilka ważnych kwestii. Atrybut enctype wskazuje, że dane będą musiały zostać podzielone na wieloczęściowy element danych. Zauważ też, że wykorzystujemy kilka nowych atrybutów: autofocus i required.

function sendForm() {

var data = new FormData(document.forms.namedItem("profileStyle "));

data.append("myfile", stylesblob);

var req = new XMLHttpRequest();

req.open("POST", "/styleStore", true);

req.onload = function(oEvent) {

if (req.status == 200) {

console.log("Styles Saved!");

} else {

console.log("Error "+req.status+" occurred uploading your file")

};

req.send(data);

}

Wymagania naszej aplikacji obejmują również przesłanie obrazu do oddzielnej zdalnej usługi internetowej. Obraz jest dostępny jako adres URL danych i chcielibyśmy dołączyć go do FormData i wysłać żądanie w tle.

var durl = $("#carter_small").attr("src")

var blob = dataURItoBlob(durl);

Aby więc dalej zademonstrować, najpierw przekonwertujmy nasz dataURI na obiekt Blob, abyśmy mogli dołączyć go do FormData. Przyjrzyjmy się bliżej dataURItoBlob():

function dataURItoBlob(dataURI) {

var byteString;

if (dataURI.split(',')[0].indexOf('base64') >= 0){

byteString = atob(dataURI.split(',')[1]);

}else{

byteString = unescape(dataURI.split(',')[1]);

}

var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]

var ab = new ArrayBuffer(byteString.length);

var ia = new Uint8Array(ab);

for (var i = 0; i < byteString.length; i++) {

ia[i] = byteString.charCodeAt(i);

}

var bb = new Blob([ab], {type: mimeString});

return bb;

}

Po przekazaniu referencji do naszej funkcji dataURItoBlob () sprawdzamy, czy jest ona zakodowana w URLE i cofamy ją, jeśli to konieczne. Następnie ustawiamy odniesienie do typu MIME, zapisujemy bajty ciągu do ArrayBuffer i przekazujemy ArrayBuffer do nowego Bloba, wraz z wartością MIME ustawioną na właściwość type. Teraz generujemy nowe FormData i dołączamy blob jako canvasImage:

var fd = new FormData (document.forms [0]);

fd.append ("canvasImage", blob);

Hack No.55

Użyj interfejsu API WebStorage, aby utrwalić dane użytkownika

Aplikacje internetowe często potrzebują sposobu na przechowywanie danych. Dzięki interfejsom API LocalStorage i SessionStorage w HTML5 możesz w prosty sposób przechowywać dane w łatwym w użyciu interfejsie API. Trwałość danych w pierwszych dniach aplikacji internetowych opierała się na jednej podstawowej technologii: plikach cookie. Chociaż pliki cookie były serwisowalne, obarczone były obawami dotyczącymi prywatności i ograniczeniami (w zakresie rozmiaru pamięci masowej i ich wpływu na wydajność sieci). Na szczęście HTML5 zapewnia znacznie ulepszony sposób obsługi trwałego przechowywania danych: LocalStorage. Technicznie nazywany WebStorage API, większość ludzi nazywa go LocalStorage. Istnieje również odpowiedni interfejs API o nazwie SessionStorage, który omówimy później.

Podstawy

LocalStorage to na podstawowym poziomie zbiór par nazwa-wartość. Na przykład mogę mieć nazwę "Ulubione piwo" z wartością "Abita Amber". W ten sposób przypominają ciasteczka. W przeciwieństwie do plików cookie same dane nie są wysyłane do serwera na każde żądanie. Zamiast tego JavaScript ma dostęp do pobierania, ustawiania lub usuwania tych wartości w razie potrzeby. Wartości LocalStorage są przechowywane na domenę. Oznacza to, że jeśli foo.com ustawi wartość LocalStorage o nazwie "FavoriteBeer", a goo.com spróbuje odczytać tę wartość, nie będzie w stanie tego zrobić. Goo.com może ustawić własną wartość o nazwie "FavoriteBeer", ale będzie ona istnieć w swoim własnym zbiorze danych i nie będzie zakłócać ani zastępować wartości LocalStorage ustawionych w innych witrynach. Na koniec powinieneś wiedzieć, że LocalStorage jest zapisywane w fizycznym pliku na komputerze użytkownika. Ma to dwie konsekwencje. Po pierwsze, całkowita ilość danych, które można przechowywać w LocalStorage, jest ograniczona. Ten limit różni się w zależności od przeglądarki, ale większość z nich włącza 5 MB (IE 8 pozwala na 10 MB danych użytkownika). Po drugie, odczyt i zapis w LocalStorage to jednowątkowa operacja we / wy na plikach. Zasadniczo "rozsądne" korzystanie z LocalStorage powinno wystarczyć. Podobnie jak pliki cookie były przydatne do przechowywania ustawień i podstawowych zestawów danych, LocalStorage może również pełnić tę samą rolę. Pamiętaj, że nie chcesz przechowywać dużych bloków danych. W tym celu możesz zamiast tego rozważyć interfejs API natywnego systemu plików. Prawdopodobnie najlepszą rzeczą w LocalStorage jest to, jak dobrze jest obsługiwana w różnych przeglądarkach, zarówno stacjonarnych, jak i mobilnych. Według caniuse.com wsparcie to obecnie 88,73%.

API

Masz dwie główne opcje pracy z LocalStorage. Możesz zapisywać i ustawiać wartości, korzystając z bezpośredniego dostępu do obiektu window.localStorage. Oto kilka przykładów:

window.localStorage.favoriteBeer = "Abita";

window.localStorage["favoriteBeer"] = "Abita";

Lub możesz użyć jednej z następujących metod:

setItem(key, value);

getItem(key);

removeItem(key);

clear();

Założę się, że wiesz, co robią pierwsze dwa. Trzeci, removeItem, umożliwia całkowite usunięcie wartości z LocalStorage. I wreszcie, wyczyść usunie wszystkie wartości. Ponownie jednak pamiętaj, że operacje na LocalStorage są specyficzne dla domeny. Wywołanie window.localStorage.clear () podczas odwiedzania foo.com nie spowoduje wyczyszczenia elementów, które zostały ustawione w goo.com. Spójrzmy na prosty przykład.

< !doctype html >

< html >

< head >

< script >

function init() {

//Do we have a value yet?

if(!localStorage.getItem("visits")) localStorage.setItem("visits",0);

//Get and increment the value

var visits = Number(localStorage.getItem("visits")) + 1;

//Display it

document.querySelector("#resultDiv").innerHTML = "You have been here"

+visits + " time(s).";

//And store it back

localStorage.setItem("visits", visits);

}

< /script >

< /head >

< body onload="init()" >

< div id="resultDiv" >< /div >

< /body >

< /html >

Ten szablon uruchamia prostą funkcję, init, po załadowaniu strony. Zaczynamy od sprawdzenia, czy klucz odwiedzin istnieje w localStorage. Jeśli tak nie jest, przyjmujemy wartość domyślną 0. Następnie pobieramy i zwiększamy wartość. Ponieważ wszystko jest przechowywane jako ciąg, zawijamy wywołanie w konstruktorze Number(), aby upewnić się, że operacja matematyczna działa. Następnie aktualizujemy DOM i wyświetlamy liczbę odwiedzin. Na koniec przechowujemy wartość z powrotem w localStorage. Wynik netto to prosta strona internetowa, która może śledzić, ile razy ją odwiedziłeś. Jeśli otworzysz go w przeglądarce i ponownie załadujesz kilka razy, zobaczysz, że licznik zwiększy się o jeden . Jeśli zamkniesz przeglądarkę i ponownie otworzysz plik HTML, będzie on kontynuowany od miejsca, w którym został przerwany. Wierz lub nie, ale to naprawdę wszystko, co jest potrzebne do API.

Pamięć lokalna i złożone dane

Jedną z rzeczy, nad którymi możesz się zastanawiać, jest sposób przechowywania złożonych danych, takich jak tablice i obiekty. Wartość przekazana do setItem musi być ciągiem. Jak więc przechowywać bardziej złożone wartości? To proste: najpierw serializujesz obiekt. Serializacja odnosi się do konwersji złożonego obiektu na prostszą formę, w naszym przypadku na ciąg. Oczywiście oznacza to, że musisz później deserializować łańcuch z powrotem do jego pierwotnej formy. Sposób serializacji danych zależy od Ciebie. Możesz zbudować własny kod, aby pobrać złożone dane i przekształcić je w ciągi. Na przykład tablice mają natywną metodę toString, która konwertuje dane na ciąg. Niektórzy wolą używać JSON do obsługi tych operacji. Działa z prawie każdą formą danych i podobnie jak localStorage ma przyzwoitą obsługę przeglądarki. (A dla tych z Was, którzy martwią się nieobsługiwanymi przeglądarkami, istnieje wiele bibliotek JSON). W następnym przykładzie formularz z wieloma wartościami jest konwertowany na jeden prosty obiekt, który jest przechowywany w localStorage. Rzućmy okiem na kod, a następnie przejdziemy przez poszczególne części.

< !doctype html >

< html >

< head >

< script >

function init() {

//If we have old data, load it

var oldData = localStorage.getItem("formdata");

if(oldData) {

var realData = JSON.parse(oldData);

document.querySelector("#yourname").value = realData.name;

document.querySelector("#yourage").value = realData.age;

document.querySelector("#youremail").value = realData.email;

document.querySelector("#yourphone").value = realData.telephone;

}

//listen for changes in our form fields

document.querySelector("#myForm").addEventListener("input",

function() {

//get all the fields

var data = {name:document.querySelector("#yourname").value,

age:document.querySelector("#yourage").value,

email:document.querySelector("#youremail").value,

telephone:document.querySelector("#yourphone").value

};

console.dir(data);

localStorage.setItem("formdata", JSON.stringify(data));

},false);



}

< /script >

< /head >

< body onload="init()" >

< form id="myForm" >

< p >

< label for="yourname" >Your Name< /label >

< input type="text" id="yourname" >

< /p >

< p >

< label for="yourage" >Your Age< /label >

< input type="number" id="yourage" min="0" >

< /p >

< p>

< label for="youremail" >Your Email< /label >

< input type="email" id="youremail" >

< /p >

< p >

< label for="yourphone" >Your Phone Number< /label >

< input type="tel" id="yourphone" >

< /p >

< /form >

< /body >

< /html >

Zaczynając od dołu, możesz zobaczyć prosty formularz z czterema unikalnymi polami: imię i nazwisko, wiek, adres e-mail i telefon. Interesująca część jest na szczycie w JavaScript. Funkcja init ma teraz dwie główne części. W pierwszej sekcji szukamy wartości LocalStorage o nazwie formdata. Jeśli istnieje, zakłada się, że jest to wersja JSON naszych danych. Używamy JSON API, aby przeanalizować to z powrotem na "rzeczywiste" dane, które możemy następnie przypisać do naszych pól formularza. Druga połowa zajmuje się przechowywaniem danych formularza. Używamy zdarzenia input dla formularzy, które jest uruchamiane za każdym razem, gdy wprowadzana jest zmiana. Bierzemy po kolei wszystkie pola i przypisujemy je do nowego obiektu o nazwie data. To jest następnie serializowane przy użyciu JSON.stringify i przechowywane w LocalStorage. Z około 10 wierszami kodu stworzyliśmy formularz, który będzie automatycznie zapisywał i przywracał wartości. Jeśli przeglądarka użytkownika ulegnie awarii lub przypadkowo zamknie przeglądarkę, żadne zmiany nie zostaną utracone. (Jeśli jesteś ciekawy, możesz użyć removeItem w module obsługi przesyłania, aby wyczyścić pola formularza).

Korzystanie z SessionStorage

Do tej pory skupialiśmy się na LocalStorage, trwałym interfejsie API do przechowywania danych w aplikacjach JavaScript. Dostępny jest również półtrwały system przechowywania o nazwie SessionStorage. Działa dokładnie tak samo, jak LocalStorage, z wyjątkiem tego, że jest oparty na sesjach, co można po prostu powiedzieć, że działa z "jednym typowym zastosowaniem" aplikacji internetowej. Gdy użytkownik zamyka przeglądarkę lub nie wchodzi w interakcję z witryną w określonym czasie można bezpiecznie założyć, że wartość zostanie zamknięta. Jak właśnie powiedziałem, API jest dokładnie takie samo. Po prostu zamień każde wywołanie localStorage na sessionStorage:

window.sessionStorage ["favouriteBeer"] = "Abita";

Obawy dotyczące bezpieczeństwa

Powinno być oczywiste, że nie należy ufać żadnym danym po stronie klienta, w tym LocalStorage. Podobnie jak nie ufasz bezwarunkowo danych przesyłanych przez XHR do twojego serwera, powinieneś założyć, że każda wartość LocalStorage może być widziana i manipulowana przez użytkowników w ich przeglądarkach.

Hack No.56

Wypełnij LocalStorage za pomocą YepNope.js i Storage.js

Chociaż interfejs API WebStorage jest szeroko obsługiwany we wszystkich głównych przeglądarkach, nadal istnieje potrzeba obsługi wypełniaczy w IE 7. Jednym z najpopularniejszych i najłatwiejszych w obsłudze programów ładujących skrypty polyfill jest YepNope.js. Remy Sharp definiuje wypełniacze w następujący sposób:

Wypełniacz polyfill lub polifiller to fragment kodu (lub wtyczka) zapewniający technologię, której programista oczekuje od przeglądarki w sposób natywny. Spłaszczenie krajobrazu API, jeśli chcesz.

Shim, polyfill i fallback to terminy używane do opisania skryptów używanych do załatania brakującego interfejsu API w określonej przeglądarce, aby programista mógł napisać przyjazny dla przyszłości kod, który działa we wszystkich głównych przeglądarkach. W tym hacku użyjemy dwóch fantastycznych bibliotek, które pozwolą nam osiągnąć 100% wsparcie dla lokalnego przechowywania we wszystkich głównych przeglądarkach: YepNope.js, jak wspomniano w streszczeniu hackowania, oraz Modernizr, biblioteka JavaScript, która współpracuje z YepNope. js do wykrywania funkcji.

Modernizr

Aby rozpocząć, najpierw potrzebujemy sposobu, aby wykryć, czy LocalStorage jest obsługiwany. Modernizr to biblioteka JavaScript, która wykrywa funkcje HTML5 i CSS3 w przeglądarce użytkownika. Zacznijmy od dodania Modernizr do strony:

< script src = "/ i / js / modernizr.com-custom-2.6.1-01.js" > < /script >

Uruchamia serię wykrywania obiektów podczas ładowania strony, zwraca wartość logiczną dla każdego testu i przechowuje dane w obiekcie JavaScript (). Modernizr zawiera YepNope.js jako opcję warunkowego ładowania zewnętrznych zasobów.js i .css. Jest to bardzo przydatne do zarządzania obsługą wypełniaczy w aplikacji.

Korzystanie z YepNope

YepNope jest opisywany jako "asynchroniczny warunkowy program ładujący zasoby, który jest superszybki i pozwala ładować tylko te skrypty, których potrzebują użytkownicy". Cóż, jak powiedziałem wcześniej, jedynym skryptem, którego potrzebujemy, jest polyfill do lokalnego przechowywania w IE 6 i 7 oraz Firefox w wersjach 2 i 3. Więc najpierw zadzwonimy do yepnope, który zawiera odniesienie do właściwości Modernizr dla localstorage. Ustawimy wartość tej właściwości do przetestowania. Jeśli test się powiedzie, przejdziemy do jego zakończenia, co spowoduje wykonanie naszego kodu aplikacji. Jeśli to się nie powiedzie, wywoła wbudowany moduł ładujący skrypt yepnope, aby pobrać plik storage.js. Funkcja wywołania zwrotnego, do której odwołuje się complete będzie również wywołana.

yepnope ({

test: Modernizr.localstorage,

nope: ['../../js/storage.js'],

complete: function (url, result, key) {

if (url === '../../js/storage.js') {

console.log('polyfill loaded');

}

// applications code

}

});

Korzystanie z Storage.js

Storage.js to biblioteka napisana przez Bretta Wejrowskiego. Skrypt wykorzystuje dane userData dla IE 6 i 7 oraz globalStorage dla Firefoksa w wersjach 2 i 3, aby zaimplementować polyfill dla localStorage, gdy nie jest on obsługiwany.

UWAGA

Zawartość pliku storage.js można wyświetlić w serwisie GitHub. Aby zademonstrować skuteczność naszego modułu ładującego wypełniacze, zaimplementujemy prosty licznik przechowujący zwiększoną wartość w localStorage. Do przycisku uruchamiającego licznik dołączymy odbiornik zdarzenia kliknięcia:

< button id = "counter" type = "button" > Kliknij mnie! < /buton >

Teraz dodamy logikę licznika do pełnego wywołania zwrotnego naszego skryptu YepNope:

yepnope({

test: Modernizr.localstorage,

nope: ['../../js/storage.js'],

complete: function (url, result, key) {

if (url === '../../js/storage.js') {

console.log('polyfill loaded');

}

function counter(){

var c = (localStorage.clickcount) ? localStorage.clickco

unt++ : localStorage.clickcount=1;

var str = "clicked " + c + " times"

$("#clicks").html(str);

}

$("#counter").on("click", function(){counter()});

}

});

Hack No.57

Buforuj zasoby multimedialne lokalnie za pomocą interfejsu API FileSystem

Dzięki interfejsowi FileSystem API możesz udostępnić przeglądarce piaskownicę, w której dane (zarówno tekstowe, jak i binarne) mogą być odczytywane i zapisywane. Jedną z potężniejszych funkcji, które wkrótce pojawią się w przeglądarce blisko Ciebie, jest możliwość odczytu i zapisu w lokalnym systemie plików. Wersję roboczą "File API: Directories and System" można znaleźć na stronie internetowej W3C.

Nazwany FileSystem API, zapewnia przeglądarce bezpieczną małą piaskownicę, w której można odczytywać i zapisywać dane (zarówno tekstowe, jak i binarne). Wiele doskonałych artykułów szczegółowo opisuje API. Ten hack zakłada, że znasz przynajmniej podstawy. Przy takim założeniu przyjrzymy się rzeczywistemu scenariuszowi, w którym dostęp do systemu plików przeglądarki może być przydatny: lokalne buforowanie zasobów multimedialnych. Wyobraź sobie przez chwilę, że tworzysz modną, nową usługę internetową. Chcesz skorzystać z kilku wysokiej jakości obrazów lub plików dźwiękowych. Teraz wyobraź sobie, że możesz spakować wszystkie te zasoby w jeden plik ZIP, wysłać go do przeglądarki i zapisać lokalnie kopię. Niekoniecznie jest to w przypadku dostępu offline, ale jako sposób na zminimalizowanie zdalnych połączeń sieciowych i po prostu odciążenie klienta. Aby być jeszcze wydajniejszym, chcesz również śledzić datę pliku ZIP , kiedy był ostatnio aktualizowany. Następnie możesz użyć lekkiego żądania sieciowego, aby sprawdzić, czy zostało zaktualizowane, zanim przejdziesz do ponownego przetwarzania. Zacznijmy!

Inicjowanie i przygotowywanie systemu plików

Jedną z pierwszych rzeczy, które robi kod demonstracyjny, jest ustalenie, czy może nawet korzystać z funkcji FileSystem API. Obecnie interfejs API jest pełen prefiksów dostawców. W przyszłości API trzeba uogólnić, ale tutaj zaczynamy dbać tylko o Chrome. Zwróć uwagę na różne prefiksy dostawców w kodzie, jak w poniższym przykładzie:

function init() {

if(!window.webkitStorageInfo) return;

}

(Jeśli jesteś ciekawy, funkcja init () jest uruchamiana przez wywołanie body / onload. Pełny szablon pojawi się wkrótce.) Procedura inicjalizacji rozpoczyna się od sprawdzenia, czy istnieje webkitStorageInfo. Ponieważ to demo dotyczy tylko demonstracji interfejsu FileSystem API, możemy natychmiast zakończyć, jeśli nie jest obsługiwane. Interfejs API FileSystem rozróżnia tymczasowy i trwały system plików. Ich imiona wskazują, kiedy należy użyć jednego nad drugim. W przypadku tej aplikacji wybierzemy trwały system plików, abyśmy mogli przechowywać nasze zasoby do czasu ich aktualizacji. Aby pracować z trwałym systemem plików, prosimy użytkownika o dostęp. Odbywa się to za pomocą funkcji JavaScript, ale faktyczny monit jest obsługiwany przez przeglądarkę, podobnie jak w przypadku geolokalizacji. Poniższy blok kodu pokazuje, jak zażądać przechowywania i co zostanie zrobione po zatwierdzeniu (lub odrzuceniu):

window.webkitStorageInfo.requestQuota(window.PERSISTENT,

20*1024*1024,

function(grantedBytes) {

console.log("I was granted "+grantedBytes+" bytes.");

window.webkitRequestFileSystem(window.PERSISTENT,

grantedBytes, onInitFs, errorHandler);

}, errorHandler);

Pamiętaj, że poprosiłeś o rozmiar, ale możliwe, że podany rozmiar będzie mniejszy. Nie martw się nawet o to, co otrzymałeś na razie. W przyszłości możesz chcieć to zapisać (localStorage) i upewnić się, że nie przekraczasz limitu. Ale ważną rzeczą do zapamiętania jest to, że po zatwierdzeniu kubła miejsca możesz zażądać właściwego systemu plików. Wywołanie webkitRequestFileSystem zwraca wskaźnik dla wszystkich przyszłych opcji odczytu / zapisu plików i katalogów. Jego moduł obsługi sukcesu, w tym przypadku onInitFs, jest uruchamiany, gdy wszystko jest gotowe. Wreszcie, nasz errorHandler jest uruchamiany, jeśli coś pójdzie nie tak. Rzućmy okiem na to, zanim przejdziemy dalej:

function errorHandler(e) {

var msg = '';

console.dir(e);

switch (e.code) {

case FileError.QUOTA_EXCEEDED_ERR:

msg = 'QUOTA_EXCEEDED_ERR';

break;

case FileError.NOT_FOUND_ERR:

msg = 'NOT_FOUND_ERR';

break;

case FileError.SECURITY_ERR:

msg = 'SECURITY_ERR';

break;

case FileError.INVALID_MODIFICATION_ERR:

msg = 'INVALID_MODIFICATION_ERR';

break;

case FileError.INVALID_STATE_ERR:

msg = 'INVALID_STATE_ERR';

break;

default:

msg = 'Unknown Error';

break;

};

console.log('Error: ' + msg);

}

Poprzedni kod służy tylko do testowania i właściwie nie przedstawia użytkownikowi żadnej przyjemnej odpowiedzi. Używa konsoli tylko do zgłaszania błędów. Upewnij się, że wykonujesz testy przy otwartej konsoli.

Praca z systemem plików

W tym momencie ustaliliśmy, że nasza przeglądarka obsługuje system plików. Poprosiliśmy użytkownika o miejsce na dane. Poprosiliśmy o wskazanie systemu plików. Po tym wszystkim, onInitFs zostaje wreszcie uruchomiony. W tym momencie dobrym pomysłem jest odświeżenie strony, aby wyjaśnić cel. Celem jest pobranie pliku ZIP, wyodrębnienie zawartości i zapisanie jej w lokalnym systemie plików. Aby to umożliwić, zaczniemy od zdefiniowania folderu, w którym przechowywane są nasze pliki. Nazwijmy ten zasób zmienny DIRLOC:

var resourceDIRLOC = "resources";

Nie ma nic specjalnego w tej nazwie, ale chcesz, aby podkatalog zawierał więcej rzeczy w przyszłości i nie musisz martwić się o organizację. Mimo że jest to piaskownica oddzielona od reszty systemu plików, ważne jest, aby traktować go jak każdy inny system plików. Nie chcesz robić bałaganu - zarówno dla dobra użytkowników, jak i dla własnego zdrowia psychicznego. Najpierw otwórzmy ten katalog. Interfejs API pozwala nam otworzyć katalog, który nie istnieje. Robimy to, przekazując flagę tworzenia. W danym momencie możemy to zrobić tylko dla jednego poziomu katalogu. Na przykład nie możemy próbować otworzyć / resources / images / highres, a API po prostu utworzy wszystkie te zagnieżdżone foldery. W takim przypadku musimy utworzyć każdy podkatalog po kolei. Na szczęście ten przykład ma prostszy cel:

function onInitFs(fs) {

fileSystem = fs;

fileSystem.root.getDirectory(fs.root.fullPath + '/' +

resourceDIRLOC, {create:true}, function(dir) {

resourceDIR = dir;

}

Skopiujmy uchwyt systemu plików, fs, do zmiennej o zasięgu globalnym. Będziemy potrzebować później fs, więc najlepiej od razu go skopiować. Następnie zadzwonimy, aby uzyskać katalog. Zauważ, że ścieżka jest oparta na jednej z właściwości uchwytu systemu plików: root.fullPath. Obiekt główny jest wskaźnikiem katalogu do ścieżki naszej piaskownicy. Pełna ścieżka to po prostu: rzeczywista ścieżka do katalogu. Łącząc to z separatorem (i pamiętaj, możesz użyć / niezależnie od tego, czy jesteś w systemie Windows, czy nie) i nazwą katalogu zasobów, mamy wtedy pełną ścieżkę do folderu do użycia. Flaga tworzenia obsługuje tworzenie po raz pierwszy. Wszystkie wywołania interfejsu FileSystem API są asynchroniczne, dlatego funkcję wywołania zwrotnego rozpoczniemy od ostatniego argumentu. Wreszcie, pierwszą rzeczą, którą zrobimy w wywołaniu zwrotnym, jest buforowanie wskaźnika do nowego katalogu. resourceDIR to zmienna globalna, której można użyć ponownie później. A teraz interesująca część: pobrany przez nas plik ZIP jest dość duży. Chcemy go pobrać tylko za pierwszym razem, a potem tylko wtedy, gdy został zmodyfikowany. Aby zapamiętać datę modyfikacji, użyjemy localStorage do buforowania. Rozważ następny blok:

if(localStorage["resourceLastModified"]) {

var xhr = new XMLHttpRequest();

xhr.open("HEAD", resourceURL );

xhr.onload = function(e) {

if(this.status == 200) {

var lastMod = this.getResponseHeader("Last-Modified");

if(lastMod != localStorage["resourceLastModified"]) {

fetchResource();

} else {

console.log("Not fetching the zip, my copy is kosher.");

}

}

}

xhr.send();

} else {

fetchResource();

}

Pierwsza część poprzedniego bloku kodu jest wykonywana, jeśli mamy wartość określającą, kiedy plik ZIP był ostatnio modyfikowany. (Wkrótce zobaczymy, gdzie to ustawić.) Jeśli istnieje resourceLastModified, tworzymy żądanie Ajax tylko z nagłówkiem. Jest to lekkie wywołanie sieciowe, które po prostu zwraca nagłówki zdalnego zasobu. Sprawdzamy nagłówek Last-Modified. Jeśli w jakikolwiek sposób jest inny, musimy ponownie ustawić nasz plik ZIP. Robi się to w wywołaniu funkcji fetchResource (). Wreszcie widzimy, że blok else po prostu uruchamia funkcję fetchResource ().

Pobieranie i przetwarzanie pliku ZIP

Przyjrzyjmy się metodzie fetchResource(). Odpowiada za pobranie zdalnego pliku ZIP, rozpakowanie go i zapisanie w systemie plików. JavaScript nie ma natywnej obsługi do pracy z plikami ZIP. Użyłem prostej, ale potężnej biblioteki zip.js napisanej przez Gildasa Lormeau. Bibliotekę zip.js można znaleźć na GitHub. Pamiętaj, że potrzebujesz tylko plików zip.js i deflate.js. Zacznijmy od spojrzenia na fetchResource:

function fetchResource() {

var xhr = new XMLHttpRequest();

xhr.responseType="arraybuffer";

xhr.open("GET", resourceURL,true);

xhr.onload = function(e) {

if(this.status == 200) {

}

}

xhr.send();

}

Poprzedni kod pokazuje części funkcji, które obsługują żądanie Ajax. Na razie ładowanie jest puste, ponieważ jest nieco skomplikowane. Zwróć uwagę na kilka rzeczy. Po pierwsze, typ odpowiedzi to bufor tablicy. Potrzebujemy tego do przetwarzania danych binarnych z ZIP. Po drugie, resourceURL to po prostu statyczny adres URL zdefiniowany wcześniej w naszym kodzie:

var resourceURL = "resources.zip";

Przyjrzyjmy się teraz kodowi, który jest uruchamiany po zakończeniu żądania:

var lastMod = this.getResponseHeader("Last-Modified");

localStorage["resourceLastModified"] = lastMod;

Pierwszą rzeczą, jaką robimy, jest buforowanie daty modyfikacji pliku ZIP. LocalStorage sprawia, że jest to niezwykle łatwe. Zanotuj klucz resourceLastModified. Kod możemy testować wielokrotnie. Możemy utworzyć nowe pliki ZIP i zaktualizować ich ostatnio zmodyfikowaną wartość za pomocą wiersza poleceń lub po prostu użyć konsoli naszej przeglądarki do usunięcia wartości.

var bb = new WebKitBlobBuilder();

bb.append(this.response);

var blob = bb.getBlob("application/zip");

Następnie przygotujemy dane binarne przed przekazaniem ich do biblioteki ZIP. Jest to proces wieloetapowy, który obejmuje Konstruktora pochodzącego z surowej odpowiedzi, a następnie rzeczywisty obiekt Blob utworzony przez określenie naszego konkretnego typu MIME dla naszych danych. Efektem końcowym są jednak dane binarne ZIP. Teraz przeanalizujmy plik ZIP:

zip.createReader(new zip.BlobReader(blob), function(reader) {

reader.getEntries(function(entries) {

entries.forEach(function(entry) {

resourceDIR.getFile(entry.filename,

{create:true},

function(file) {

entry.getData(new zip.FileWriter(file), function(e) {

}, function(current, total) {

// onprogress callback

});

});

});

});

}, function(err) {

console.log("zip reader error!");

console.dir(err);



Poprzedni kod jest prawdopodobnie nieco zagmatwany, ponieważ mamy callbacki wywołujące callbacki. W skrócie zaczynamy od stworzenia instancji czytnika ZIP. Jest to oparte na interfejsie API zip.js. Jednym z wielu sposobów zainicjowania instancji czytnika ZIP jest przekazanie naszego obiektu blob. Następnie zapewniamy wywołanie zwrotne w celu obsługi czytnika. W ramach tego wywołujemy getEntries() na czytniku. To pozwala nam wyliczyć każdy element w pliku ZIP. To jest punkt, w którym zaczynamy zapisywać dane do systemu plików. Pamiętasz resourceDIR? To tylko wskaźnik do naszego katalogu. Użyjemy go do tworzenia w nim plików, wywołując metodę getFile (). Podamy nazwę opartą na nazwie wpisu pliku ZIP. Tak więc, jeśli pierwszy wpis w naszym ZIP to foo.jpg, entry.filename to foo.jpg. Następnie getFile() otwiera plik w systemie plików. W ramach procedury obsługi sukcesu możemy użyć entry, czyli pliku w pliku ZIP, i wyciągnąć dane za pomocą getData ). Zasadniczo otwieramy plik w systemie plików i przenosimy bity z wpisu pliku ZIP do pliku, który otworzyliśmy. Pierwszym argumentem metody getData jest program do zapisywania plików. To obsługuje rzeczywiste bity. Dwa puste wywołania zwrotne mogą opcjonalnie monitorować postęp. Ale ponieważ jest to stosunkowo prosty proces (znowu wysysanie bitów z jednej rzeczy do drugiej), możemy na razie zostawić je w spokoju. I to wszystko. Aby przetestować, użyłem doskonałej wtyczki do Chrome HTML5 FileSystem Explorer, rozszerzenia, które umożliwia przeglądanie systemu plików powiązanego z witryną internetową. Zbudowałem też prostą funkcję, która renderuje kilka z tych obrazów:

document.querySelector("#testButton").addEventListener("click", function() {

//Attempt to draw our images that exist in the file system

//If they exist, we draw from there, if not, we do not display them.

var images = ["bobapony.jpg",

"buy bacon.jpg",

"cool boba.jpg",

"chuck-norris.jpg"

];

for(var i=0, len=images.length; i
var thisImage = images[i];

resourceDIR.getFile(thisImage, {create:false},

function(file) {

document.querySelector("#images").innerHTML +=

"< img src='"+file.toURL() + "'>
";

});

}

}, false);

Gdy użytkownik kliknie przycisk, kod przechodzi w pętlę przez tablicę nazw plików, aby sprawdzić, czy istnieją one w systemie plików. Jeśli tak, kod po prostu dodaje obraz do DOM. Zwróć uwagę na użycie file.toURL (). Użyłem tego wywołania, aby uzyskać odniesienie do obrazu, do którego mogę się odwołać z HTML.

Dokąd iść stąd

Mam nadzieję, że ten hack dał ci pomysł, co możesz zrobić z systemem plików. Chociaż wsparcie jest nadal nieco ograniczone, korzyści wynikające z możliwości lokalnego przechowywania zasobów sprawiają, że jest to więcej niż warte zachodu, nawet jeśli API jest w toku. Miej oko na roboczą wersję roboczą File API W3C pod kątem aktualizacji.

Hack No.58

Zbuduj kalendarz kamieni milowych za pomocą IndexedDB i FullCalendar.js

IndexedDB to trwała składnica danych obiektów w przeglądarce. Chociaż nie jest to pełna implementacja SQL i jest bardziej złożona niż nieustrukturyzowane pary klucz-wartość w localStorage, można jej użyć do zdefiniowania interfejsu API, który zapewnia możliwość odczytu i zapisu obiektów klucz-wartość jako ustrukturyzowanych obiektów JavaScript oraz system indeksowania ułatwiający filtrowanie i wyszukiwanie. W przypadku tego hacka użyjemy IndexedDB do przechowywania obiektów kamieni milowych dla aplikacji kalendarza. Interfejs użytkownika zapewni prosty sposób tworzenia nowego kamienia milowego i zapewni tytuł, datę rozpoczęcia i datę zakończenia. Kalendarz zostanie następnie zaktualizowany, aby wyświetlić zawartość lokalnego magazynu danych. Musimy zacząć od uwzględnienia znaczników dla dwóch elementów interfejsu użytkownika: kalendarza i formularza. Zaczniemy od formularza. Możesz zauważyć, że pola wejściowe dla dat zawierają atrybuty data-data-format. Użyjemy ich później w zbieracza dat JavaScript

< form >

< fieldset >

< div class="control-group" >

< label class="control-label" >Add a Milestone< /label >

< div class="controls" >

< h2 >New Milestone< /h2 >

< input type="text" name="title" value="" >

< input type="text" class="span2" name="start" value="07/16/12" data-date-format="mm/dd/yy" id="dp1" >

< input type="text" class="span2" name="end" value="07/17/12" data-date-format="mm/dd/yy" id="dp2" >

< /div >

< /div >

< div class="form-actions" >

< button type="submit" class="btn btn-primary Save"< /buton >

< button class="btn" >Cancel< /buton >

< /div >

< /fieldset >

< /form >

Kalendarz jest dostarczany przez FullCalendar.js, fantastyczną wtyczkę jQuery do generowania solidnych kalendarzy ze źródeł zdarzeń. Biblioteka wygeneruje kalendarz z obiektu konfiguracyjnego i prostego elementu div.

< div id='calendar' >< /div >

Nie możemy zapomnieć o uwzględnieniu kilku zależności:

< link href="../assets/css/datepicker.css" rel="stylesheet" >

< link href="../assets/css/fullcalendar.css" rel="stylesheet" >

< script src=http://code.jquery.com/jquery-1.7.1.min.js >< /script >

< script src="../assets/js/bootstrap-datepicker.js" >< /script >

< script src="../assets/js/fullcalendar.min.js" >< /script >

Aby poprawić wrażenia użytkownika, uwzględnimy również narzędzia do wybierania dat w polach formularza dla dat rozpoczęcia i zakończenia. Aby utworzyć instancję selektorów dat, uwzględnimy następujące elementy w pliku początek naszego skryptu:

$(function(){

$('#dp1').datepicker();

$('#dp2').datepicker();

});

Milestone IndexedDB

Teraz skonfigurujemy globalną przestrzeń nazw do przechowywania naszego kodu i skonfigurujemy publiczną tablicę kamieni milowych (w przestrzeni nazw), aby tymczasowo przechowywać nasze kamienie milowe podczas przekazywania ich między naszą bazą danych a interfejsem API FullCalendar. Powinno to mieć więcej sensu, gdy będziesz kontynuować czytanie. Gdy już to zrobimy, będziemy musieli znormalizować naszą zmienną indexedDB we wszystkich właściwościach specyficznych dla dostawcy.

var html5hacks = {};

html5hacks.msArray = [];

var indexedDB = window.indexedDB || window.webkitIndexedDB ||

window.mozIndexedDB;

if ('webkitIndexedDB' in window) {

window.IDBTransaction = window.webkitIDBTransaction;

window.IDBKeyRange = window.webkitIDBKeyRange;

}

Teraz możemy przystąpić do konfigurowania naszej bazy danych:

html5hacks.indexedDB = {};

html5hacks.indexedDB.db = null;

function init() {

html5hacks.indexedDB.open();

}

init();

To oczywiście na razie się nie powiedzie, ale jak widać, inicjalizacja rozpoczyna się od wywołania metody open () na html5hacks.indexedDB. Przyjrzyjmy się więc bliżej open ():

html5hacks.indexedDB.open = function() {

var request = indexedDB.open("milestones");

request.onsuccess = function(e) {

var v = "1";

html5hacks.indexedDB.db = e.target.result;

var db = html5hacks.indexedDB.db;

if (v!= db.version) {

var setVrequest = db.setVersion(v);

setVrequest.onerror = html5hacks.indexedDB.onerror;

setVrequest.onsuccess = function(e) {

if(db.objectStoreNames.contains("milestone")) {

db.deleteObjectStore("milestone");

}

var store = db.createObjectStore("milestone",

{keyPath: "timeStamp"});

html5hacks.indexedDB.init();

};

}

else {

html5hacks.indexedDB.init();

}

};

request.onerror = html5hacks.indexedDB.onerror;

}

Najpierw musimy otworzyć bazę danych i podać nazwę. Jeśli baza danych zostanie pomyślnie otwarta i zostanie nawiązane połączenie, wywołanie zwrotne onsuccess () zostanie uruchomione. W ramach onsuccess, sprawdzamy wersję i wywołujemy setVersion (), jeśli taka nie istnieje. Następnie wywołamy createObjectStore () i przekażemy unikalny znacznik czasu we właściwości keypath. Na koniec wywołujemy init (), aby zbudować kalendarz i dołączyć zdarzenia obecne w bazie danych.

html5hacks.indexedDB.init = function() {

var db = html5hacks.indexedDB.db;

var trans = db.transaction(["milestone"], IDBTransaction.READ_WRITE);

var store = trans.objectStore("milestone");

var keyRange = IDBKeyRange.lowerBound(0);

var cursorRequest = store.openCursor(keyRange);

cursorRequest.onsuccess = function(e) {

var result = e.target.result;

if(!result == false){

$('#calendar').fullCalendar({

header: {

left: 'prev,next today',

center: 'title',

right: 'month,agendaWeek,agendaDay'

},

weekmode: 'variable',

height: 400,

editable: true,

events: html5hacks.msArray

});

return;

}else{

console.log("result.value" , result.value);

buildMilestoneArray(result.value);

result.continue();

}

};

cursorRequest.onerror = html5hacks.indexedDB.onerror;

};

W tym momencie jesteśmy gotowi pobrać wszystkie dane z bazy danych i wypełnić nasz kalendarz kamieniami milowymi. Najpierw deklarujemy typ transakcji jako READ_WRITE, ustawiamy odniesienie do datastore, ustawiamy zakres kluczy i definiujemy żądaniekursora, wywołując openCursor i przekazując zakres kluczy. Przekazując 0, zapewniamy, że pobieramy wszystkie wartości większe od zera. Ponieważ naszym kluczem był znacznik czasu, zapewni to pobranie wszystkich rekordów. Po uruchomieniu zdarzenia onsuccess zaczynamy iterować przez rekordy i wypychać obiekty kamieni milowych do buildMilestoneArray:

function buildMilestoneArray(ms) {

html5hacks.msArray.push(ms);

}

Kiedy docieramy do ostatniego rekordu, budujemy kalendarz przekazując konfigurację

object to fullCalendar() and returning:

$('#calendar').fullCalendar({

header: {

left: 'prev,next today',

center: 'title',

right: 'month,agendaWeek,agendaDay'

},

weekmode: 'variable',

height: 400,

editable: true,

events: html5hacks.msArray

});

return;

Dodawanie kamieni milowych

Teraz, gdy inicjalizujemy i budujemy nasz kalendarz, musimy rozpocząć dodawanie kamieni milowych do bazy danych za pośrednictwem formularza. Najpierw użyjmy jQuery, aby skonfigurować nasz formularz do przekazywania zserializowanego obiektu danych do addMilestone () przy każdym przesłaniu:

$('form').submit(function() {

var data = $(this).serializeArray();

html5hacks.indexedDB.addMilestone(data);

return false;

});

Prześlijmy teraz kilka wydarzeń, a następnie wyświetlmy je w Chrome Inspector, aby upewnić się, że tam są. Przyjrzyjmy się bliżej naszej metodzie addMilestone:

html5hacks.indexedDB.addMilestone = function(d) {

var db = html5hacks.indexedDB.db;

var trans = db.transaction(["milestone"], IDBTransaction.READ_WRITE);

var store = trans.objectStore("milestone");

var data = {

"title": d[0].value,

"start": d[1].value,

"end": d[2].value,

"timeStamp": new Date().getTime()

};

var request = store.put(data);

var dataArr = [data]

request.onsuccess = function(e) {

$('#calendar').fullCalendar('addEventSource', dataArr);

};

request.onerror = function(e) {

console.log("Error Adding: ", e);

};

};

Ustanowiliśmy nasze połączenie odczytu / zapisu w podobny sposób jak nasza html5hacks.indexedDB.init (), ale teraz zamiast tylko odczytywać dane, za każdym razem zapisujemy obiekt danych do magazynu danych, wywołując metodę store.put () i przekazywanie jej danych. Po osiągnięciu sukcesu możemy wywołać metodę addEventSource () fullcalendar i przekazać jej dane opakowane w obiekt tablicy. Zwróć uwagę, że konieczne jest przekształcenie obiektu danych w tablicę, ponieważ tego oczekuje interfejs API FullCalendar.

Hack No.59

Użyj interfejsów API geolokalizacji, aby wyświetlić długość i szerokość geograficzną w mobilnej aplikacji internetowej

Interfejs API Geolokalizacji udostępnia łatwy w użyciu interfejs API. Wystarczy kilka wierszy kodu, aby uzyskać informacje o aktualnej pozycji użytkownika. Co więcej, jQuery Mobile zapewnia prostą strukturę do tworzenia aplikacji internetowych na urządzenia mobilne dla różnych przeglądarek. W tym hacku wykorzystamy platformę jQuery Mobile, aby zapewnić stosunkowo proste sposoby tworzenia aplikacji mobilnej dla różnych przeglądarek. Ponieważ ten hack koncentruje się na wyświetlaniu naszej aktualnej długości i szerokości geograficznej oraz korzystaniu z API w internecie mobilnym będziemy potrzebować tylko prostego interfejsu użytkownika.

Prosta aplikacja mobilna jQuery

Jak zawsze zaczniemy od stworzenia podstawowej strony z wykorzystaniem HTML5 i uwzględnienia naszych zależności:

< !DOCTYPE html >

< html lang="en" >

< head >

< title >jQuery Mobile GeoLocation demo< /title >

< meta name="viewport" content="width=device-width, initial-scale=1" >

< link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.css" / >

< script src=http://code.jquery.com/jquery-1.7.1.min.js >< /script >

< script src=http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js >< /script >

< /head >

< body >

// jQuery mobile declarative markup here

< /body >

< /html >

Jak widać, zadeklarowaliśmy zależność od jednego arkusza stylów i trzech skryptów JavaScript. Pozostałą część aplikacji zbudujemy przy użyciu deklaratywnych znaczników HTML i atrybutów danych, które zostaną zinterpretowane przez platformę jQuery Mobile. W tagu możemy teraz umieścić:

< div data-role="page" data-theme="a" >

< div data-role="header" >

< h1 >Geo Location< /h1 >

< /div >< !-- /header -- >

< div data-role="content" >

< ul data-role="listview" data-inset="true" >

< li >

< a href="./longlat-embed.html" data-ajax="false" >LongLat< /a >

< /li >

< /ul >

< /div >< !-- /content -- >

< /div >< !-- /page -- >

Jak można się było spodziewać, interfejs użytkownika nie został stworzony za pomocą magii. jQuery Mobile używa JavaScript do wykorzystywania atrybutów danych obecnych w znacznikach HTML, aby dynamicznie generować więcej HTML i CSS. Efektem końcowym jest to, co widzisz w swojej przeglądarce. Teraz utworzymy oddzielną stronę, do której będziemy prowadzić linki. Wielu z was zauważyło link do longlat-embed.html na stronie głównej.

< ul data-role="listview" data-inset="true" >

< li >

< a href="./longlat-embed.html" data-ajax="false" >LongLat< /a >

< /li >

< /ul >

To przeniesie nas do strony, na której będzie działać nasz JavaScript, który zawiera nasz kod geolokalizacji. Zauważ, że wyznaczyliśmy, aby nie była to strona jQuery Mobile Ajax. Gwarantuje to, że po kliknięciu linku przejdziemy do nowej strony. Ważne jest, aby strona, do której prowadzi łącze, została wczytana, tak aby wykonywał się jej JavaScript. Ta strona ma podobną strukturę do innej strony, z tymi samymi zależnościami. Celowo utrzymywałem kod jQuery Mobile tak prosto, jak to tylko możliwe. Więcej informacji na temat pracy z jQuery Mobile można znaleźć w doskonałym zestawie dokumentacji dostępnej na ich stronie internetowej.

< div data-role="page" data-theme="a" >

< div data-role="header" >

< h1 >LongLat< /h1 >

< /div >< !-- /header -- >

< div data-role="content" >

< /div >< !-- /content -- >

< /div >< !-- /page -- >

W treści utworzymy element div, który będzie zawierał nasze dane dotyczące długości i szerokości geograficznej po ich zwróceniu z usługi zdalnej. Dodamy również możliwość powrotu do poprzedniej strony.

< div class="geo-coords" >

GeoLocation: < span id="Lat">lat: ...< /span >o,

< span id="Long" >long: ...< /span >o
< /div >

< a href="./jqueryMobile-embed.html"

data-role="button"

data-inline="true" >

Back

< /a >

Teraz zajmiemy się naszą geolokalizacją JavaScript. Jeśli znasz jQuery, początkowa zmienna $ będzie wyglądać znajomo w poniższym kodzie. Jeśli nie, możesz dowiedzieć się więcej o jQuery online. Mówiąc najprościej, opakowanie funkcji jQuery zapewnia, że nasza strona jest gotowa, zanim wykonamy następujący skrypt. Następnie skonfigurujemy globalny obiekt przestrzeni nazw, którego będziemy używać do przechowywania naszych danych. Ten rodzaj organizacji będzie ważny, ponieważ nasz skrypt będzie się stawał coraz bardziej złożony. Następnie sprawdzimy, czy bieżąca przeglądarka obsługuje geolokalizację, sprawdzając obiekt nawigatora pod kątem obecności właściwości geolokalizacji. Jeśli będzie dostępna, wywołamy metodę getCurrentPosition i przekażemy obiekt sukcesu i błędu. Następnie skonstruujemy obiekt sukcesu i błędu. W naszym obiekcie sukcesu możemy zaakceptować pozycję jako parametr i zapytać obiekt o jego zagnieżdżony obiekt współrzędnych, który zawiera zarówno właściwości szerokości i długości geograficznej. Następnie wywołamy funkcję populateHeader (), która używa jQuery do dołączenia zwróconych wartości do tagów span, które zawierają identyfikatory Lat i Long.

$(function() {

var Geo={};

if (navigator.geolocation) {

navigator.geolocation.getCurrentPosition(success, error);

}

//Get the latitude and the longitude;

function success(position) {

Geo.lat = position.coords.latitude;

Geo.lng = position.coords.longitude;

populateHeader(Geo.lat, Geo.lng);

}

function error(){

console.log("Geocoder failed");

}

function populateHeader(lat, lng){

$('#Lat').html(lat);

$('#Long').html(lng);

}

});

Wróćmy teraz do naszej przeglądarki i przejdź do nowej strony. Gdy użytkownik uzyskuje dostęp do strony internetowej zawierającej wywołanie navigator.geolocation.getCurrentPosition(), zostaje wyświetlone powiadomienie dotyczące bezpieczeństwa a pasek zostanie wyświetlony na górze strony. Przeglądarki obsługujące interfejs API geolokalizacji mają własne powiadomienie bezpieczeństwa, które prosi użytkownika o zezwolenie lub odmowę dostępu przeglądarki do aktualnej lokalizacji urządzenia. Jeśli użytkownik zezwoli aplikacji internetowej na śledzenie swojej fizycznej lokalizacji, skrypt będzie nadal wykonywał i wysyła żądanie do serwera informacji o lokalizacji. Serwer zdalny zwraca zestaw danych zawierający długość i szerokość geograficzną. Po uzyskaniu informacji i wywołaniu funkcji zwrotnej succes () aktualizujemy stronę.

Kwestie bezpieczeństwa i prywatności

Możliwość gromadzenia przez twórców aplikacji internetowych danych o lokalizacji użytkowników końcowych budzi sporo obaw w odniesieniu do bezpieczeństwa i prywatności. Specyfikacja W3C jasno wskazuje, że aplikacje klienckie powinny powiadamiać użytkowników i udostępniać interfejs do autoryzacji korzystania z danych o lokalizacji, umożliwiając im określenie, którym aplikacjom internetowym ufają:

Programy użytkownika nie mogą wysyłać informacji o lokalizacji do witryn sieci Web bez wyraźnej zgody użytkownika. Programy użytkownika muszą uzyskać pozwolenie za pośrednictwem interfejsu użytkownika, chyba że zostało to wcześniej uzgodnione relacje zaufania z użytkownikami, jak opisano poniżej. Interfejs użytkownika musi zawierać składnik hosta identyfikatora URI [URI] dokumentu. Te uprawnienia, które są uzyskiwane za pośrednictwem interfejsu użytkownika i które są zachowane poza bieżącą sesją przeglądania (tj. Poza czasem, w którym kontekst przeglądania [BROWSINGCONTEXT] jest przenoszony do innego adresu URL) muszą być odwołane, a programy użytkownika muszą przestrzegać odwołanych uprawnień

Hack No.60

Użyj interfejsu Google Geocoding API, aby odwrócić geokodowanie lokalizacji użytkownika

Dane dotyczące długości i szerokości geograficznej są korzystne dla aplikacji tylko wtedy, gdy może ona zrobić coś bardziej interesującego niż tylko je wyświetlić. Jednym z typowych przypadków użycia jest odwrócenie geokodowania lub znalezienie lokalizacji czytelnej dla człowieka na podstawie długości i szerokości geograficznej. Wcześniej zhakowaliśmy prostą aplikację internetową na telefon komórkowy, która wyświetlała aktualną długość i szerokość geograficzną użytkownika. W tym hacku użyjemy tej samej aplikacji jQuery Mobile i dodamy dodatkowy przycisk do ekranu głównego. Ten przycisk przeniesie nas na oddzielną stronę, która wyświetla najbliższe miasto i stan w oparciu o naszą aktualną lokalizację. Najpierw dodajmy dodatkowy przycisk:

< ul data-role="listview" data-inset="true" >

< li >

< a href="./longlat-embed.html" data-ajax="false" >

LongLat

< /a >

< /li >

< li >

< a href="./location-name-embed.html" data-ajax="false" >

Location By Name

< /a >

< /li >

< /ul >

Musimy również pamiętać o dołączeniu interfejsów API Map Google do obsługi naszego wezwania do usługi geokodowania (więcej na ten temat później):

< script src=http://maps.googleapis.com/maps/api/js? sensor=false >

< /script >

Mamy teraz dwa przyciski na naszym ekranie głównym jQuery Mobile. To prowadzi nas do nowej strony, na której zostanie uruchomiony skrypt JavaScript, który wywołuje zdalną usługę Google. Skrypt ma podobną strukturę jak wcześniejszy skrypt długości i szerokości geograficznej, więc nie będę tutaj powtarzał tych szczegółów. Po sprawdzeniu istnienia właściwości geolokalizacji obiektów nawigatora, tworzymy zmienną geokodera i tworzymy nową instancję google.maps.Geocoder():

if (navigator.geolocation) {

geocoder = new google.maps.Geocoder();

navigator.geolocation.getCurrentPosition(success, error);

}

W naszym obiekcie sukcesu wyznaczamy wywołanie zwrotne, które zostanie wykonane. Tam możemy dodać naszą przyszłą metodę reverseGeo().

function success(position) {

Geo.lat = position.coords.latitude;

Geo.lng = position.coords.longitude;

reverseGeo(Geo.lat, Geo.lng);

}

Teraz utworzymy metodę reverseGeo (), która przyjmie długość i szerokość geograficzną zwróconą przez getCurrentPosition (). Najpierw pobierzemy nasze dane o szerokości i długości geograficznej i przekażemy je do funkcji pomocniczej google.maps.LatLng. Następnie ustawimy tę wartość na właściwość latLng pustego obiektu.

function reverseGeo(lat, lng) {

var latlng = new google.maps.LatLng(lat, lng);

// make the call to a geocode service

}

Metoda geocode () spowoduje wywołanie interfejsu API Google Geocoding i zwróci duży zestaw wyników zawierający więcej informacji niż potrzebujemy. Dlatego będziemy musieli wyodrębnić miasto i stan z zestawu wyników. Wartość naszego miasta jest przechowywana we właściwości o nazwie locality, a stan jest przechowywany na poziomie administracyjnym_obszar_1. Kod służący do tego jest następujący;

geocoder.geocode({'latLng': latlng}, function(results, status) {

if (status == google.maps.GeocoderStatus.OK) {

if (results[1]) {

var addressComponents = results[0].address_components;

for (var i = 0; i < addressComponents.length; i++) {

for (var b = 0; b < addressComponents[i].types.length; b++){

if (addressComponents[i].types[b] == "locality") {

city = addressComponents[i];

break;

}

var adminString = "administrative_area_level_1";

if (addressComponents[i].types[b] == adminString) {

state = results[0].address_components[i];

break;

}

}

}

Geo.location_name = city.long_name + ", " + state.short_name;

$('#storeLocation').html(Geo.location_name);

} else {

console.log("No results found");

}

}else {

console.log("Geocoder failed due to: " + status);

}

});

Oto ostateczny skrypt:

$(function() {

var Geo={};

var geocoder;

if (navigator.geolocation) {

geocoder = new google.maps.Geocoder();

navigator.geolocation.getCurrentPosition(success, error);

}

//Get the latitude and the longitude;

function success(position) {

Geo.lat = position.coords.latitude;

Geo.lng = position.coords.longitude;

populateHeader(Geo.lat, Geo.lng);

reverseGeo(Geo.lat, Geo.lng);

}

function error(){

console.log("Geocoder failed");

}

function populateHeader(lat, lng){

$('#Lat').html(lat);

$('#Long').html(lng);

}

function reverseGeo(lat, lng) {

var latlng = new google.maps.LatLng(lat, lng);

geocoder.geocode({'latLng': latlng},

function(results, status) {

if (status == google.maps.GeocoderStatus.OK) {

if (results[1]) {

var addressComponents = addressComponents;

for (var i = 0; i < addressComponents.length; i++) {

for (var b=0;b< addressComponents.length;b++){ if (addressComponents[i].types[b] == "locality") {

city = addressComponents[i];

break;

}

var adminString = "administrative_area_level_1";

if (addressComponents[i].types[b] == adminString){

state = results[0].address_components[i];

break;

}

}

}

Geo.location_name = city.long_name+", "+state.short_name;

$('#storeLocation').html(Geo.location_name);

} else {

console.log("No results found");

}

} else {

console.log("Geocoder failed due to: " + status);

}

});

}

});

Hack No.61

Zaktualizuj aktualną lokalizację użytkownika na mapie Google

Czasami użytkownik Twojej aplikacji jest w ruchu i lokalizacja musi być aktualizowana w regularnych odstępach czasu. Możesz użyć mapy Google, aby wyświetlić ruchomą pinezkę lokalizacji użytkownika.

Korzystanie z Google Maps API

W tym hacku zaczniemy od uwzględnienia niezbędnych zależności do wyświetlania map Google w naszej aplikacji. Na szczęście interfejs API Map Google sprawia, że jest to bardzo proste. Pamiętaj tylko, aby uwzględnić zależność w nagłówku dokumentu aplikacji.

< script type="text/javascript" src="http://maps.google.com/maps/api /js?libraries=geometry,places&sensor=false" >< /script >

Potrzebujemy również pustego elementu div do wypełnienia naszą mapą:

< div id = "map" > < /div >

Dołączenie mapy jest tak proste, jak ustawienie odniesienia do elementu HTML, który chcemy wypełnić, utworzenie obiektu konfiguracyjnego o nazwie configObj i przekazanie obu do instancji nowej mapy Google:

var configObj = {

zoom: 15,

center: latlng,

mapTypeControl: false,

navigationControlOptions: {

style: google.maps.NavigationControlStyle.SMALL},

mapTypeId: google.maps.MapTypeId.ROADMAP

};

var map = new google.maps.Map(document.getElementById("map"),

configObj);

Teraz, gdy mamy działającą mapę Google, zacznijmy aktualizować naszą mapę za pomocą pinezki. W tym celu użyjemy metody getCurrentPosition () do określenia naszej lokalizacji.

if (navigator.geolocation) {

navigator.geolocation.getCurrentPosition(success, error)

} else {

error('not supported');

}

Aktualizacja bieżącej lokalizacji za pomocą timera

Aplikacja, która może się automatycznie aktualizować, oczywiście ułatwiłaby życie naszym użytkownikom, nie prosząc ich o aktualizowanie lokalizacji podczas przemieszczania się. Aby włączyć automatyczną aktualizację, utworzymy licznik czasu korzystający z metody navigator.geolocation.watchPosition (). Ta metoda wykona pomyślne wywołanie zwrotne, gdy zmieni się lokalizacja lub gdy urządzenie poprawi dokładność.

var positionTimer = navigator.geolocation.watchPosition(

function(position){

updateMarker(

marker,

position.coords.latitude,

position.coords.longitude

);

}

);

Na koniec wywołamy updateMarker (), który wewnętrznie wywołuje metodę setPosition () interfejsu API Map Google:

function updateMarker(pin, lat, long){

pin.setPosition(

new google.maps.LatLng(

lat,

long

)

);

}

Poprawianie mapy

Cała reszta, w tym kod naszej mapy Google, będzie znajdować się w pomyślnym wywołaniu zwrotnym, które jest wywoływane, gdy użytkownik zezwoli na określenie swojej lokalizacji, a usługa zwróci długość i szerokość geograficzną. Właściwość position jest dostępna w obiekcie wywołania zwrotnego sukcesu, więc pobieramy stamtąd szerokość i długość geograficzną i przekażemy je do metody LatLng() interfejsu API Map Google:

var latlng = new google.maps.LatLng(position.coords.latitude,

position.coords.longitude);

Teraz ustawimy nasz prosty marker. Właściwość map jest odniesieniem do lokalnej zmiennej HTML, której wcześniej używaliśmy do przechowywania obiektu Map.

marker = new google.maps.Marker({

position: latlng,

map: map,

title:"Html5 Hacks!"

});

Poniższy kod pozwala nam umieścić kółko wokół pinezki mapy, aby wizualnie podkreślić lokalizację. (Pamiętaj, aby wypróbować różne wartości z właściwością promienia). Kiedy już to zrobimy, wyśrodkujemy również mapę do bieżącej lokalizacji.

var circle = new google.maps.Circle({

map:map,

radius:300

});

circle.bindTo('center', marker, 'position');

map.setCenter(

new google.maps.LatLng(

position.coords.latitude,

position.coords.longitude

)

);

Poprawa dokładności

We wstępie do tego rozdziału wymieniłem szereg technologii (lokalizacja publicznego adresu IP, triangulacja wieży komórkowej, GPS, adresy MAC WiFi / Bluetooth i siły sygnału), których dane urządzenie może wykorzystać do przekazania parametrów do serwera informacji o lokalizacji w celu oszacowania lokalizacja użytkownika. Metoda getCurrentPosition (), jeśli jest używana z domyślną konfiguracją, zazwyczaj wykorzystuje pierwszą i najszybszą usługę dostępną na urządzeniu, która niekoniecznie jest najdokładniejsza. Najczęstszym przykładem jest smartfon korzystający z adresu IP przed GPS. Wiadomo, że określanie lokalizacji na podstawie adresu IP jest bardzo niedokładne. Atrybut enableHighAccuracy stanowi wskazówkę, że aplikacja chciałaby uzyskać najlepsze możliwe wyniki. Może to skutkować wolniejszymi czasami odpowiedzi lub zwiększonym zużyciem energii. Użytkownik może również odmówić tej możliwości lub urządzenie może nie być w stanie dostarczyć dokładniejszych wyników, niż gdyby flaga nie została określona.

Oszczędzanie energii i / lub przepustowości

Jednym z pierwotnych zamiarów programu enableHighAccuracy było zapewnienie twórcom aplikacji na telefony komórkowe opcji unikania korzystania z GPS, który zwykle zużywa znaczną ilość energii. Tak więc, chociaż może to poprawić dokładność, jest też druga strona tej historii. Często konieczne jest zachowanie równowagi między dokładnością a zużyciem zasobów. Dostępnych jest kilka atrybutów, które mogą pomóc w podjęciu tych decyzji podczas projektowania aplikacji:

maximumAge : Maksymalny wiek (ms) ostatniej odpowiedzi lokalizacji (urządzenie może buforować odpowiedzi lokalizacji w celu oszczędzania energii i / lub przepustowości)

timeout : Maksymalny czas (ms) do ponownego podjęcia przez urządzenie wyszukiwania lokalizacji

Hack No.62

Skorzystaj z usługi Geoloqi, aby zbudować Geofence

Dzięki Geoloqi możesz przesyłać wiadomości i wykonywać zdarzenia do pojedynczego użytkownika końcowego w momencie, gdy przekroczy on strefę geofence, zamieszka w niej lub opuści strefę. A dzięki pakietowi Geoloqi JavaScript SDK możesz zbudować aplikację rozpoznającą lokalizację, która daje użytkownikowi możliwość tworzenia geofence. W tym hacku zbudujemy prostą strefę geofence, wirtualną granicę dla rzeczywistego obszaru geograficznego. Zrobimy to podróżując do naszych ulubionych miejsc w centrum Austin w Teksasie. (W końcu potrzebowaliśmy dobrej wymówki, żeby się wydostać.)

udowanie Geofence

Gdy przechodzimy z jednego zakładu do drugiego, oznaczymy i przekażemy nową lokalizację do usługi Geoloqi w celu przechowywania danych w zdalnym magazynie danych. Aby zademonstrować ogrodzenie, ponownie prześledzimy nasze kroki i zadzwonimy, aby poprosić o najbliższą oznaczoną lokalizację. Gdy będziemy wracać przez oznaczone lokalizacje, usługa Geoloqi wykona niezbędną logikę, aby określić, która zapisana lokalizacja jest najbliższa naszej bieżącej lokalizacji. To tylko niewielka część API Geoloqi, ponieważ pełna eksploracja wykraczałaby poza zakres tej książki. Miejmy nadzieję, że wzbudzi to Twoje zainteresowanie na tyle, abyś sam zaczął hakować tę usługę. Pełne API można znaleźć online.

Pierwsze kroki

Najpierw musimy założyć konto na geoloqi.com. Gdy już będziemy mieć konto, utworzymy nową aplikację. Zostaniemy przekierowani do ekranu zawierającego identyfikator klienta, klucz tajny klienta i token dostępu do aplikacji. Po uzyskaniu tych informacji możemy rozpocząć tworzenie naszej aplikacji.

Tworzenie aplikacji Geofencing

Zaczniemy od zbudowania formularza jQuery Mobile. Gdy już będziemy mieli wszystkie zależności jQuery Mobile, dołączymy następujący formularz:

< form >

< fieldset >

< h2 >New Location< /h2 >

< input type="text" name="name" value="" >

< button type="submit" class="btn btn-primary" >Save< /button >

< button onclick="getLastLocation(); return false" class="btn" >

Get Last Location

< /button >

< button onclick="getNearbyLocation();return false" class="btn" >

Get Nearby Location

< /button >

< /fieldset >

< /form >

Formularz ma trzy główne cele. Przycisk z type = "submit" wysyła nową lokalizację do usługi Geoloqi, wywołując metodę addLocation (). Drugi przycisk zwraca ostatnią dodaną lokalizację. Trzeci przycisk zwraca lokalizację najbliższą naszej aktualnej lokalizacji. Aby ukończyć niezbędne znaczniki HTML dla naszego interfejsu, uwzględnimy również następujące symbole zastępcze dla wszelkich dynamicznych aktualizacji interfejsu użytkownika:

< span id="lastLocation" >...< /span >

< span id="nearbyLocation" >...< /span >

Wywołanie API Geoloqi

Aby wywołać interfejs API Geoloqi, najpierw musimy uwzględnić interfejsy API Geoloqi JavaScript w dokumencie naszej aplikacji:

< script type = "text / javascript"

src = http://api.geoloqi.com/js/geoloqi.min.js > < /script > Musimy również zainicjować bibliotekę Geoloqi i przekazać access_token, który otrzymaliśmy podczas tworzenia naszej aplikacji na geoloqi.com:

geoloqi.init ();

geoloqi.auth = {

'access_token': '142b6-cfb41aaca58aed5f73b58085e1ff21cf6ae0c9a7'};

Następnie, tak jak to zrobiliśmy w innych trikach w tym rozdziale, użyjemy navigator.geolocation.getCurrentPosition (). Przejrzyj te hacki, jeśli chcesz uzyskać więcej szczegółów, ale oto ich sedno:

Geo = {};

if (navigator.geolocation) {

navigator.geolocation.getCurrentPosition(success, error);

}

//Get the latitude and the longitude;

function success(position) {

Geo.lat = position.coords.latitude;

Geo.lng = position.coords.longitude;

}

function error(){

console.log("GeoLocation failed");

}

Teraz zacznijmy tworzyć nasze programy obsługi zdarzeń. Najpierw musimy stworzyć funkcję, która zaakceptuje współrzędne zwrócone z naszego wywołania navigator.geolocation.getCurrentPosition () i utrwalić je w Geoloqi:

function addLocation() {

geoloqi.post("place/create", {

latitude: Geo.lat,

longitude: Geo.lng,

radius: 100,

name: "Lavaca Street Bar"

}, function(response, error){

console.log(response, error)

});

}

Aby otrzymać ostatnią wpisaną przez nas lokalizację, tworzymy metodę getLastLocation ():

function getLastLocation() {

geoloqi.get('place/list', function(result, error) {

$('#lastLocation').html(result.places[0].name);

});

}

Aby znaleźć naszą najbliższą lokalizację w naszym geofence, używamy:

function getNearbyLocation() {

geoloqi.get('place/nearby', {

latitude: Geo.lat,

longitude: Geo.lng,

radius: 100

}, function(result, error){

$('#nearbyLocation').html(result.nearby[0].name);

});

}

Wreszcie, aby ustawić zarówno wyzwalacz (zdarzenie, takie jak wiadomość SMS lub powiadomienie push), jak i miejsce w jednym połączeniu, utworzymy obiekt o nazwie geonote, który zostanie wywołany, gdy znajdziemy się w odległości 100 metrów od lokalizacji. Tak więc w ramach naszej metody addLocation () zmienimy post z miejsca na geonote:

function addLocation() {

geoloqi.post("geonote/create", {

latitude: Geo.lat,

longitude: Geo.lng,

radius: 100,

name: "You are getting close to Lavaca Street Bar"

}, function(response, error){

console.log(response, error)

});

}

Teraz otrzymamy wiadomość, gdy zbliżymy się do naszych ulubionych miejsc w centrum Austin w Teksasie. Gotowy produkt wygląda następująco:

geoloqi.init();

geoloqi.auth={'access_token':'142b6-... ae0c9a7'};

Geo = {};

if (navigator.geolocation) {

navigator.geolocation.getCurrentPosition(success, error);

}

//Get the latitude and the longitude;

function success(position) {

Geo.lat = position.coords.latitude;

Geo.lng = position.coords.longitude;

}

function error(){

console.log("GeoLocation failed");

}

function getLastLocation() {

geoloqi.get('place/list', function(result, error) {

$('#lastLocation').html(result.places[0].name);

});

}

function addLocation() {

geoloqi.post("geonote/create", {

latitude: Geo.lat,

longitude: Geo.lng,

radius: 100,

name: "You are getting close to Lavaca Street Bar"

}, function(response, error){

console.log(response, error)

});

}

function getNearbyLocation() {

geoloqi.get('place/nearby', {

latitude: Geo.lat,

longitude: Geo.lng,

radius: 100

}, function(result, error){

$('#nearbyLocation').html(result.nearby[0].name);

});

}

To dopiero początek tego, co możesz zrobić z interfejsem API geolokalizacji i usługą Geoloqi. Potraktuj to jako inspirację i stwórz bardziej ambitny hack!

Hack No.63

Skorzystaj z usługi Geoloqi Real-Time Streaming Service, aby transmitować ruch zdalnego użytkownika

Połączenie interfejsu API geolokalizacji i serwera Geoloqi Websockets stwarza świat możliwości dla innowacyjnych hacków. Pełnodupleksowy, dwukierunkowy transport protokołu WebSocket umożliwia aktualizowanie w czasie rzeczywistym lokalizacji zdalnego użytkownika na mapie. Protokół WebSocket tworzy pojedyncze, trwałe połączenie przez gniazdo TCP między klientem a serwerem, umożliwiając dwukierunkową dystrybucję komunikatów w trybie pełnego dupleksu bez narzutu związanego z nagłówkami HTTP i plikami cookie. W tym hacku użyjemy tego lekkiego protokołu do przesłania naszej lokalizacji użytkownika z powrotem do serwera.

Usługa przesyłania strumieniowego w czasie rzeczywistym Geoloqi

Przesyłanie strumieniowe w czasie rzeczywistym Geoloqi jest dostarczane za pośrednictwem Node.js i Socket.IO. Socket.IO normalizuje różne mechanizmy transportu do obsługi w czasie rzeczywistym. Jeśli przeglądarka nie obsługuje protokołu WebSocket, powróci do obsługi polyfill z jednego z następujących alternatywnych mechanizmów transportu:

•  Gniazdo Adobe Flash

•  Długie odpytywanie Ajax

•  Wieloczęściowe przesyłanie strumieniowe Ajax

•  Zawsze iFrame

•  Polling JSON

Dodanie Geoloqi JavaScript SDK jest tak proste, jak dodanie kilku tagów script w nagłówku dokumentu:

< script type="text/javascript" src=https://subscribe.geoloqi.com/socket.io/socket.io.js >< /script >

< script type="text/javascript" src=http://api.geoloqi.com/js/geoloqi.min.js >< /script >

Teraz musimy się upewnić, że mamy token udziału konta testowego Geoloqi. Oczywiście zastąpilibyśmy go prawdziwym tokenem udostępniania, gdy będziemy gotowi do wdrożenia go z prawdziwymi użytkownikami.

window.onload = function () {

var trip = new geoloqi.Socket('trip', 'TQ4ew3Z');

trip.events.location = function(location) {

console.log(location);

}

trip.events.disconnect = function() {

console.log('trip socket disconnected');

}

trip.start();

}

Jeśli otworzymy konsolę, zobaczymy w niej rejestrowane obiekty. Obiekty te zawierają informacje związane z lokalizacją użytkownika ze współdzielonym tokenem za każdym razem, gdy nasza aplikacja sonduje lokalizację. Najpierw dodamy element div z id = "map":

< div id = "map" > < /div >

Teraz dodajmy mapę Google, aby wyświetlić lokalizację użytkownika. Wcześniej omówiliśmy bardziej szczegółowo korzystanie z interfejsu API Map Google. W przypadku tego hacka mapa będzie prosta. Utwórz nową mapę i przekaż ją do metody setDefault () interfejsu API Geoloqi Maps:

window.onload = function () {

map = new google.maps.Map (document.getElementById ('map'), {zoom: 10,

center: nowe google.maps.LatLng (0, 0),

mapTypeId: google.maps.MapTypeId.ROADMAP

});

geoloqi.maps.setDefault (map);

}

Teraz, gdy mamy mapę, musimy skonfigurować nowe gniazdo, tak jak to zrobiliśmy w pierwszym przykładzie. Ponownie użyjemy konta testowego, aby utworzyć wystąpienie nowego geoloqi.Socket. Akceptuje typ gniazda, którym może być trip lub grupa. Typ grupy umożliwia programiście subskrybowanie aktualizacji lokalizacji dla wszystkich użytkowników w grupie przy użyciu tokenu grupy. Na razie użyjemy tokena podróży, aby zobaczyć tylko jednego użytkownika:

var remoteUser = null;

var sckt = new geoloqi.Socket('trip', 'TQ4ew3Z');

sckt.events.location = function(location) {

if(remoteUser === null) {

remoteUser = geoloqi.maps.pins.Basic(

{position: new google.maps.LatLng(

location.latitude,

location.longitude)

});

}

remoteUser.moveTo(new google.maps.LatLng(

location.latitude,

location.longitude),

true);

}

sckt.start();

}

Teraz powinniśmy zobaczyć, jak pinezka porusza się po mapie w czasie rzeczywistym w synchronizacji ze zdalnym użytkownikiem, którego śledzimy. Aby przetestować to samodzielnie, utwórz konto i zastąp token podróży własnym.

Hack No.63

Wypełniaj interfejsy API geolokalizacji za pomocą elementów Webshims

Często programiści aplikacji internetowych mają za zadanie osiągnięcie zgodności między przeglądarkami z funkcjonalnością, do której dążą w aplikacji. Interfejs API geolokalizacji nie jest dostępny natywnie w IE 8 i starszych, ale istnieją alternatywne wypełnienia, takie jak webshimy, których możesz użyć do wykonania zadania. Na początek uwzględnimy nasze zależności:

< script src=http://code.jquery.com/jquery-1.7.1.min.js >< /script >

< script src="js/modernizr.js" >< /script >

< script src="js/yepnope.js" >< /script >

Skonfigurujemy kilka podstawowych znaczników do wypełnienia po otrzymaniu naszych współrzędnych lokalizacji:

< div class = "geo-coords" >

GeoLocation: lat: ... < /span >o,

< span id = "Long" > long: ... < /span > o

< /div >

Teraz dołączymy moduł ładujący skrypt yepnope, który używa właściwości Modernizr.geolocation do testowania przeglądarki pod kątem obsługi geolokalizacji. Jeśli wywołanie nope zwróci wartość true, yepnope dynamicznie załaduje plik polyfiller.js z biblioteki webshims, łatając przeglądarkę z obsługą geolokalizacji. Po zakończeniu wywołanie zwrotne zostanie uruchomione. Na razie przetestujemy te warunki za pomocą prostego alertu dotyczącego adresu URL wypełnienia:

yepnope({

test: Modernizr.geolocation,

nope: ['../../js/polyfiller.js'],

callback: function (url, result, key) {

// test yepnope loader

alert(url);

}

});

Kiedy odświeżamy przeglądarkę, która nie obsługuje geolokalizacji, na przykład IE 8 i starsze, powinniśmy zobaczyć alert. Więc teraz możemy go usunąć i zastąpić naszym skryptem webshims. Najpierw ustawimy opcję confirmText, aby skonfigurować komunikat wyświetlany użytkownikowi. Następnie wywołamy polyfill () i przekażemy funkcje, które chcemy dodać. W przypadku tego włamania będziemy musieli tylko dodać geolokalizację. Teraz możemy wywołać navigator.geolocation.getCurrentPosition () i przekazać obiekty wywołania zwrotnego sukcesu i błędu:

$.webshims.setOptions({

geolocation: {

confirmText: 'obtaining your location.

}

});

//load all polyfill features

$.webshims.polyfill('geolocation');

$.webshims.polyfill();

$(function() { var Geo={};

function populateHeader(lat, lng){

$('#Lat').html(lat);

$('#Long').html(lng);

}

//Get the latitude and the longitude;

function success(position) {

Geo.lat = position.coords.latitude;

Geo.lng = position.coords.longitude;

populateHeader(Geo.lat, Geo.lng);

}

function error(){

console.log("Geocoder failed");

}

navigator.geolocation.getCurrentPosition(success, error);

});

And here it is, all together:

yepnope({

test: Modernizr.geolocation,

nope: ['../../js/polyfiller.js'],

callback: function (url, result, key) {

$.webshims.setOptions({

waitReady: false,

geolocation: {

confirmText: '{location} wants to know your position.

}

});

//load all polyfill features

$.webshims.polyfill('geolocation);

$.webshims.polyfill();

$(function() {

var Geo={};

function populateHeader(lat, lng){

$('#Lat').html(lat);

$('#Long').html(lng);

}

//Get the latitude and the longitude;

function success(position) {

Geo.lat = position.coords.latitude;

Geo.lng = position.coords.longitude;

populateHeader(Geo.lat, Geo.lng);

}

function error(){

console.log("Geocoder failed");

}

if (navigator.geolocation) {

navigator.geolocation.getCurrentPosition(success, error);

}

});

}

});

Hack No.64

Jak przeglądarki obsługują JavaScript

JavaScript działa w tym samym wątku, co reszta interfejsu użytkownika przeglądarki, a jako interpreter pobiera programy obsługi zdarzeń z pętli zdarzeń, gdy są w kolejce do wykonania. Ta kolejka jest współużytkowana ze wszystkimi programami obsługi zdarzeń i obejmuje te zainicjowane przez interakcję użytkownika. Wcześniej porównamy postrzeganą wydajność manipulacji tablicą zarówno w głównym wątku, jak iw ramach dedykowanego pracownika. Jeśli nie byłeś świadkiem możliwości kompensowania pracy pracownikowi, podczas pierwszego włamania otrzymasz wyraźną demonstrację. Baza kodu JavaScript stale rośnie, a im więcej uruchomionych skryptów JavaScript, tym więcej aplikacji blokuje się, czekając na zakończenie kodu. Ostatecznie otrzymujemy nieodpowiadające komunikaty skryptu. Jak często widziałeś okno dialogowe z informacją, że niektóre skrypty na stronie trwają zbyt długo, a Twoja przeglądarka całkowicie przestała odpowiadać? Co może spowodować brak reakcji w przeglądarce? Oto kilka bardziej powszechnych przypadków, z których niektóre omówimy w następujących hackach:

•  Przetwarzanie dużych tablic lub odpowiedzi JSON

•  Wstępne pobieranie i / lub buforowanie danych do późniejszego wykorzystania

•  Analiza danych wideo lub audio

•  Usługi internetowe z ankietami

•  Filtrowanie obrazu w Canvas

•  Aktualizacja wielu wierszy lokalnej bazy danych magazynu

•  Kodowanie / dekodowanie dużego ciągu

Hack No.65

Użyj interfejsu BlobBuilder, aby utworzyć wbudowany proces roboczy

Czasami programiści muszą przechowywać skrypty i znaczniki razem w jednym pliku HTML. W razie potrzeby możesz utworzyć pracownika wbudowanego za pomocą interfejsu BlobBuilder. Ten hack nauczy Cię, jak analizować wsadowe dane z Facebooka. Jak wspomniałem we wstępie do tego rozdziału, istnieją trzy typy pracowników sieciowych: wbudowane, dedykowane i współdzielone. W tym hacku wykorzystamy wbudowanego pracownika internetowego, wykorzystując interfejs BlobBuilder. W przeciwieństwie do pracowników dedykowanych i współdzielonych, pracownicy inline nie wymagają utrzymywania pracownika w zewnętrznym skrypcie. Pracownicy wbudowani są przydatni, ponieważ pozwalają deweloperowi na utworzenie samodzielnej strony. Korzystając z interfejsu BlobBuilder, możesz "wstawić" pracownika w tym samym pliku HTML, co główna logika, tworząc BlobBuilder i dołączając kod procesu roboczego jako ciąg. Najlepszym sposobem myślenia o obiekcie Blob jest plik DOM. Interfejs BlobBuilder zapewnia intuicyjny sposób konstruowania obiektów Blob. Jeśli znasz metody pobierające i ustawiające, API powinno mieć dla ciebie dużo sensu. Po prostu utwórz nową instancję BlobBuilder i użyj metody append (), aby ustawić w niej dane. Następnie możesz użyć metody getBlob (), aby pobrać cały obiekt BLOB zawierający dane. Zaczniemy od ustawienia typu na skrypt JavaScript / Worker, aby interpretery JavaScript go nie przeanalizowały:

< script id = "worker1" type = "JavaScript / worker" >

self.onmessage = function (event) {

var jsonText = event.data;

// analizuje strukturę

var jsonData = JSON.parse (jsonText);

// odeślij wyniki

// Zapętlaj dane i odeślij obiekty z

// nazwa zespołu i numer talk_about_count

self.postMessage (jsonData);

}



Interfejs API Facebook Graph i odpowiedzi grupowe

Często pożądane jest zredukowanie żądań HTTP aplikacji do jak najmniejszej liczby, jeśli nie do pojedynczego żądania. W tym hacku chcemy pobrać dane ze stron fanowskich dużej liczby zespołów z określonego gatunku. Dane, które nas interesują, to właściwość talking_about_count. Ta właściwość jest miernikiem, który ma mierzyć sukces stron na Facebooku, aw naszej aplikacji jesteśmy zainteresowani monitorowaniem "szumu" zespołów z naszego wybranego gatunku. Właściwość talking_about_count odpowiada następującym zachowaniom użytkowników Facebooka:

•  Polubienia strony

•  Liczba wpisów na ścianie strony

•  Komentowanie treści na stronie

•  Udostępnianie aktualizacji statusu strony

•  Odpowiadanie na pytanie opublikowane przez stronę

•  Odpowiadanie na wydarzenie hostowane przez stronę

•  Wzmianka o stronie

•  Oznaczanie strony na zdjęciu

•  Polubienie lub udostępnienie oferty zameldowania

•  Odprawa w miejscu

Masz pomysł. Planujemy wykonać operacje na tych danych. Możliwości pracy z takimi danymi są nieograniczone, więc w ramach tego hacka po prostu przeanalizujemy tę konkretną właściwość, utworzymy nieuporządkowaną listę i zaktualizujemy interfejs użytkownika.

Zmniejszenie partii do pojedynczego żądania

Chcemy być zoptymalizowani pod kątem urządzeń mobilnych; Chcemy również buforować dane lokalnie, dlatego zdecydowaliśmy się skorzystać z obsługi wsadowej interfejsu Facebook Graph API, aby wykonać jedno żądanie dotyczące wszystkich naszych danych. Obsługa wsadowa Facebooka umożliwia przekazywanie instrukcji dotyczących kilku operacji w jednym żądaniu HTTP. Możesz także określić zależności między powiązanymi operacjami (opisane w skrócie). Facebook będzie przetwarzał każdą niezależną operację równolegle i będzie przetwarzał operacje zależne po kolei. Po zakończeniu wszystkich operacji skonsolidowana odpowiedź zostanie przesłana z powrotem, a połączenie HTTP zostanie zamknięte.

Budowanie Bloba

Najpierw użyjemy dość powszechnego podejścia, aby pobrać zawartość roboczą z naszego pracownika wbudowanego i dołączyć ją do obiektu Blob. Będziemy używać następujących metod specyficznych dla dostawcy:

var bb = new (window.BlobBuilder || window.WebKitBlobBuilder ||

window.MozBlobBuilder) ();

Teraz pobierzemy zawartość za pomocą metody querySelector () i

Właściwość textContent naszego wybranego elementu:

bb.append (document.querySelector ('# worker1'). textContent);

Na koniec musimy utworzyć blobURL i plik roboczy. Nasz proces roboczy zużywa parametr, który w rzeczywistości jest odniesieniem do skryptu. Dlatego parametr używa metody createObjectURL () do utworzenia adresu URL do naszego wcześniej utworzonego obiektu Blob.

var objUrl = (window.webkitURL || window.URL);

var worker = new Worker(objUrl.createObjectURL(bb.getBlob()));

Pobieranie danych z Facebook Graph API

Teraz możemy pobrać potrzebne dane, wywołując publiczny interfejs API Graph w serwisie Facebook. Więc najpierw ustawimy nasz adres URL, upewniając się, że callback jest ustawiony na? aby przezwyciężyć problemy międzydomenowe z JSONP:

var url = "https://graph.facebook.com/?ids=TheFlecktones,

umphreysmcgee, SpearheadRecords & callback =? ";

Następnie użyjemy metody .getJSON () jQuery do utworzenia obiektu XMLHttpRequest, serializacji odpowiedzi i przekazania jej do naszego procesu roboczego. W tym momencie osiągnęliśmy nasz cel, jakim jest przeniesienie operacji parsowania z głównego wątku przez wywołanie metody Worker.postMessage () i przekazanie danych.

$.getJSON(url, function(json) {

console.log(json)

data = JSON.stringify(json);

worker.postMessage(data); // Start the worker.

});

Jeśli korzystasz z Google Chrome lub Safari, naciśnij Option-Command-I, aby otworzyć Narzędzia programistyczne, a następnie przejdź do konsoli. Teraz zaktualizujemy naszego pracownika za pomocą logiki, aby nasze dane były nieco interesujące. Musimy nim trochę manipulować, więc najpierw utworzymy nową tablicę, która będzie zawierała proste obiekty o dwóch właściwościach: name i talking_about_count.

var arr = new Array();

for (var key in jsonData) {

if (jsonData.hasOwnProperty(key)) {

arr.push({ "name": jsonData[key].name,

"count": jsonData[key].talking_about_count

});

}

}

Teraz, gdy mamy tę nową prostą tablicę, posortujmy ją, tworząc podstawową funkcję compare (), którą możemy przekazać do natywnej metody sortowania JavaScript. W rezultacie nasze liczby są teraz w porządku malejącym.

function compare(a,b) {

if (a.count < b.count)

return 1;

if (a.count > b.count)

return ?1;

return 0;

}

var newarr = arr.sort(compare);

All in all, our new inline worker looks like this:

< script id="worker1" type="JavaScript/worker" >

self.onmessage = function(event) {

var jsonText = event.data;

var jsonData = JSON.parse(jsonText);

var arr = new Array();

for (var key in jsonData) {

if (jsonData.hasOwnProperty(key)) {

arr.push({ "name": jsonData[key].name, "count":

jsonData[key].talking_about_count});

}

}

function compare(a,b) {

if (a.count < b.count)

return 1;

if (a.count > b.count)

return ?1;

return 0;

}

var newarr = arr.sort(compare);

//send back to the results

self.postMessage(newarr);

}

< /script >

Teraz możemy skonfigurować naszego słuchacza, aby odpowiadał na wiadomości przychodzące od pracownika. Gdy odzyskamy dane z naszego wątku roboczego, zajmiemy się tym, aktualizując nasz interfejs użytkownika. Najpierw przyjrzyjmy się danym, analizując je w konsoli. Powinniśmy zobaczyć, że nasze dane są teraz minimalne, tylko z nazwą i właściwościami talking_about_count. Ponadto prążki powinny być posortowane w porządku malejącym przez talking_about_count.

worker.onmessage = function (event) {

// struktura JSON jest przekazywana z powrotem

var jsonData = event.data;

console.log (jsonData);

};

Teraz nasz moduł obsługi onmessage () w naszym głównym wątku zaktualizuje interfejs użytkownika nowymi danymi z Facebooka. Możemy użyć jQuery, aby dołączyć listę do interfejsu użytkownika:

var list = $('
    ').attr('class','list');

    $("#status").append(list);

    for (var i = 0; i < newarr.length; i++) {

    var listitem = $('
  • ').attr('class','listitem')

    .text(newarr[i].name + " : " + newarr[i].count)

    $("#status > ul").append(listitem);

    };

    Hack No.66

    Wykonuj ciężkie obliczenia tablicowe w dedykowanym programie roboczym sieci Web

    Dedykowani pracownicy sieciowi są doskonałym rozwiązaniem w przypadku kosztownych obliczeń, takich jak manipulacja tablicą. Dzięki temu hackowi możesz przenieść takie obliczenia do pracownika bez wpływu na wydajność interaktywnej animacji płótna w głównym wątku. Aby zacząć rozumieć siłę pracowników internetowych, musimy wykonać kosztowną operację i stworzyć interfejs użytkownika wykorzystujący pewien rodzaj animacji, z którym chcielibyśmy, aby użytkownik mógł bezproblemowo wchodzić w interakcje. W przypadku kosztownej operacji możemy manipulować danymi tablicowymi, a dla animacji możemy stworzyć proste płótno, w którym piłki odbijają się, gdy użytkownik wchodzi z nimi w interakcję. Następnie udostępnimy prosty interfejs użytkownika, który zapewni narzędzia do testowania ciężkich manipulacji tablicami wewnątrz i na zewnątrz pracownika internetowego. Test będzie koncentrował się na wrażeniach użytkownika. Nie będziemy przedstawiać danych pokazujących wydajność kodu ur, ale będziemy opierać się na popularnej koncepcji postrzeganej wydajności, aby określić, które rozwiązanie jest bardziej wydajne.

    Kosztowne obliczenia

    Najpierw utwórzmy funkcję, która manipuluje danymi tablicowymi, pobierając dane wejściowe z dwóch dużych liczb całkowitych i wyświetlając dwuwymiarową tablicę wszystkich kombinacji tych dwóch liczb. Przy mniejszych liczbach wykonywanie tych operacji jest dość dobrze wykonywane poza narzędziem roboczym sieciowym lub w głównym wątku we wszystkich nowoczesnych przeglądarkach. Ale gdy zaczynamy zwiększać rozmiar wejściowych liczb całkowitych, nasz interfejs użytkownika i animacja płótna zaczynają wykazywać powolność. Nasze obliczenia zostaną zachowane wewnątrz funkcji process (). Na końcu skryptu uzyskamy dostęp do elementu w naszym znaczniku z identyfikatorem obszaru tekstowego i przekażemy mu ciąg "PRZETWARZANIE ZAKOŃCZONE" oraz ustawimy tło na czerwono. Daje nam to jasny wskaźnik, kiedy przetwarzanie macierzy jest zakończone.

    function process() {

    console.log("WITHOUT WORKER")

    var r = $('select#row').val();

    var c = $('select#col').val();

    var a = new Array(r);

    for (var i = 0; i < r; i++) {

    a[i] = new Array(c);

    for (var j = 0; j < c; j++) {

    a[i][j] = "[" + i + "," + j + "]";

    }

    }

    var complete = "PROCESSING COMPLETE";

    $('#textarea').text(complete);

    $('#textarea').css({'background-color': '#BF3831',

    'color': '#fff'});

    };

    Musimy również stworzyć prosty interfejs użytkownika, aby zademonstrować nasz test pracownika. Stwórzmy więc pola wyboru zawierające wartości, które przekażemy do naszych obliczeń. Jedno pole wyboru uruchomi funkcję process (), a drugie uruchomi funkcję processWorker (), która wykona te same obliczenia wewnątrz procesu roboczego (więcej o funkcji processWorker () w dalszej części tego rozdziału). Możemy również dodać obszar tekstu, do którego odwołujemy się w naszym poprzednim skrypcie przetwarzania.

    < form class="form-horizontal" >

    < fieldset >

    < div class="control-group" >

    < label class="control-label"

    for="select01" >Select Row Value< /label >

    < div class="controls" >

    < select id="row" >

    < option >choose a value< /option >

    < option >1000< /option >

    < option >2000< /option >

    < option >3000< /option >

    < option >4000< /option >

    < /select >

    < /div >



    < div class="control-group" >

    < label class="control-label" for="select01" >

    Select Column Value< /label >

    < div class="controls" >

    < select id="col" >

    < option >choose a value< /option >

    < option >1000

    < option >2000< /option >

    < option >3000< /option >

    < option >4000< /option >

    < /select >

    < /div >

    < /div >

    < div class="control-group" >

    < label class="control-label" for="textarea" >

    Output< /label >

    < div class="controls" >

    < textarea class="input-xlarge" id="textarea"

    rows="1" >< /textarea >

    < /div >br>
    < /div >br>
    < div class="control-group" >

    < label class="control-label" for="textarea" >

    Process< /label >

    < div class="controls" >

    < button id="worker" class="btn-small

    btn-danger" href="#" >With Web Worker< /button >

    < button id="non-worker" class="btn-small

    btn-primary" href="#" >Without Web Worker< /button >

    < /div >br>
    < /div >

    < /fieldset >

    < /form >

    Musimy również użyć jQuery, aby dodać kilka detektorów zdarzeń do nasłuchiwania zdarzeń kliknięcia w polach wyboru. Procedury obsługi zdarzeń w naszych wybranych polach to odpowiednio process () i processWorker (). Na koniec dodamy funkcję init (), która nasłuchuje zdarzenia onload okna i inicjuje nasze skrypty.

    function init() {

    $('#worker').click(function() {

    var complete = "PROCESSING WITH WEB WORKER";

    $('#textarea').text(complete);

    processWorker();

    });

    $('#non-worker').click(function() {

    var complete = "PROCESSING WITHOUT WEB WORKER";

    $('#textarea').text(complete);

    process();

    });

    }

    window.onload = init;

    Bouncing Balls Canvas

    Teraz uwzględnijmy animację płótna. Użyjemy zmodyfikowanej wersji istniejącej wersji demonstracyjnej płótna dostarczonej przez Erica Rowella. Do naszych celów potrzebujemy tylko animacji, z którą możemy wchodzić w interakcje. Najpierw dodajmy trochę stylu do naszego elementu canvas:

    < style type="text/css" >

    #canvas {

    width: 575px;

    height: 300px;

    background-color: #000;

    cursor: pointer;

    }

    #myCanvas {

    border: 1px solid #9C9898;

    }

    .thing {

    position:absolute;

    }

    .dying {

    color:#ff0000;

    font-weight:bold;

    }

    < /style >

    Teraz możemy dodać skrypt. Nie uwzględnimy tutaj długiego skryptu Erica, ale możesz go zobaczyć w towarzyszącym repozytorium Git. Powinniśmy teraz mieć w pełni działającą demonstrację. Kiedy zaczynamy zwiększać rozmiar wartości w polach wyboru i wciskamy przycisk Without Web Worker, powinniśmy zauważyć, że interakcja między kulkami w obszarze roboczym staje się coraz wolniejsza.

    function processWorker() {

    console.log("WORKER")

    var r = $('select#row').val();

    var c = $('select#col').val();

    var worker = new Worker('assets/js/demojs/twoDarray-worker.js');

    var message = {

    "compfn": "create2Darray",

    "rowValue": r,

    "colValue": c

    }

    worker.postMessage(JSON.stringify(message));

    worker.onmessage = function (event) {

    // print results of array in result div

    // var data = event.data

    // Must stringify before appending to DOM

    // console.log('data has returned as: ' + typeof data

    + ' ...time to stringify and append to DOM');

    var complete = "PROCESSING COMPLETE";

    $('#textarea').text(complete);

    $('#textarea').css({'background-color': '#BF3831',

    'color': '#fff'});

    };

    };

    Tworzenie dedykowanego pracownika sieci

    Interfejs API WebWorker jest prosty i łatwy do rozpoczęcia. Najpierw uzyskamy wartości z naszych pól wyboru i zapiszemy je w zmiennych r i c. Następnie utworzymy nową instancję procesu roboczego przy użyciu nowego operatora w klasie Worker i przekazując identyfikator URI wskazujący na nasz skrypt roboczy.

    function processWorker() {

    console.log("WORKER")

    var r = $('select#row').val();

    var c = $('select#col').val();

    var worker = new Worker('assets/js/demojs/twoDarray-worker.js');



    Teraz stworzymy obiekt, który zostanie przekazany naszemu pracownikowi. Jak zobaczysz za chwilę, nasz pracownik będzie zorganizowany jako biblioteka metod. Ten obiekt jest w rzeczywistości obiektem konfiguracyjnym, który ma właściwość compfn, która instruuje bibliotekę roboczą, jak analizować dane. Następnie przekażemy nasze dane pracownikowi za pośrednictwem postMessage ().

    var message = {

    "compfn": "create2Darray",

    "rowValue": r,

    "colValue": c

    }

    Musimy również serializować nasze dane przed przekazaniem ich pracownikowi. Aby to zrobić, możemy użyć metody JSON.stringify ().

    worker.postMessage (JSON.stringify (wiadomość));

    Zanim stworzymy naszego pracownika, zakończmy naszą pracę w głównym wątku, ustawiając słuchacza do obsługi wszelkich wiadomości wysyłanych z powrotem do głównego wątku przez pracownika. Tutaj tak naprawdę nie interesują nas zwracane dane, tylko to, że zostało uruchomione wywołanie zwrotne wskazujące, że przetwarzanie zostało zakończone.

    worker.onmessage = function (event) {

    var complete = "PROCESSING COMPLETE";

    $('#textarea').text(complete);

    $('#textarea').css({'background-color': '#BF3831',

    'color': '#fff'});

    };

    Oto zakończona funkcja przetwarzania z wykorzystaniem interfejsu API WebWorker:

    function processWorker() {

    console.log("WORKER")

    var r = $('select#row').val();

    var c = $('select#col').val();

    var worker = new Worker('assets/js/demojs/twoDarray-worker.js');

    var message = {

    "compfn": "create2Darray",

    "rowValue": r,

    "colValue": c

    }

    worker.postMessage(JSON.stringify(message));

    worker.onmessage = function (event) {

    var complete = "PROCESSING COMPLETE";

    $('#textarea').text(complete);

    $('#textarea').css({'background-color': '#BF3831',

    'color': '#fff'});

    };

    };

    Teraz musimy stworzyć nasz skrypt roboczy. Wcześniej opisałem format skryptu jako bibliotekę i możliwość obsługi metody compfn (), która pozwala nam podzielić funkcjonalność na różne metody w bibliotece. Aby dopasować się do typowych konwencji branżowych, zmienną Computations wykorzystujemy wielką literą, aby wskazać, że tworzymy pseudoklasę JavaScript. Create2darray to tylko jedno z wielu obliczeń, które moglibyśmy uwzględnić w tej bibliotece roboczej. Jeśli chodzi o zakres tego hackowania, opracujemy tylko jedną metodę, ale ważne jest, abyś zrozumiał siłę tego wzorca w przyszłości.

    Computations = {

    create2Darray: function (data) {

    var r = data.rowValue;

    var c = data.colValue;

    var a = new Array(r);

    for (var i = 0; i < r; i++) {

    a[i] = new Array(c);

    for (var j = 0; j < c; j++) {

    a[i][j] = "[" + i + "," + j + "]";

    }

    }

    return a;

    }

    };

    Sekret wzorca naszej biblioteki pracowniczej jest przechowywany w detektorze onmessage (). Używamy składni nawiasów, aby wyciągnąć nazwę funkcji z obiektu konfiguracyjnego, który przekazaliśmy z naszego głównego wątku. Używamy tej funkcji, aby dopasować funkcję o tej nazwie w klasie Computations i przekazać resztę danych do przetworzenia przez tę funkcję.

    self.addEventListener ('wiadomość', funkcja (e) {

    var message = JSON.parse (e.data)

    computated = Computations [message.compfn] (message);

    self.postMessage (obliczone);

    }, fałszywe);

    Na koniec, oto nasz skrypt roboczy w całości:

    var Computations = {

    create2Darray: function (data) {

    var r = data.rowValue;

    var c = data.colValue;

    var a = new Array (r);

    for (var i = 0; i
    a [i] = nowy Array (c);

    for (var j = 0; j
    a [i] [j] = "[" + i + "," + j + "]";

    }

    }

    return a;

    }

    };

    self.addEventListener ('wiadomość', funkcja (e) {

    var message = JSON.parse (e.data)

    computated = Computations [message.compfn] (message);

    self.postMessage (obliczone);

    }, false);

    Hack No.67

    Użyj timera, aby wysłać stan aplikacji do pracowników

    Połączenie timerów i pracowników internetowych otwiera nowe możliwości w rozwoju aplikacji HTML5 zorientowanych na klienta. Możliwa jest sztucznie inteligentna (w bardzo prosty sposób) aplikacja, nadając jej zestaw funkcji wywoływanych w regularnych odstępach czasu. Chociaż te operacje można wykonywać w głównym wątku, często optymalne jest wykonywanie ich w oddzielnym procesie roboczym. W tym hacku zaczniemy inspirować się postem Angusa Crolla "pracownicy sieci kontra szalone muchy". W większości wykorzystamy większość projektu Angusa, w którym animuje on muchy, które są eliminowane z upływem czasu na podstawie manipulacji tablicami wykonywanych przez pracowników sieci. Zrobimy to samo, ale dodamy nasz własny smak. W hacku zbadamy bardzo podstawowe koncepcje sztucznej inteligencji, wykorzystując liczniki czasu JavaScript i generowanie liczb losowych do stworzenia wizualizacji internetowej. Można by argumentować, że nie jest to sztuczna inteligencja, ale jest to podstawowa symulacja inteligencji, która pokazuje dwa główne podproblemy: lokalizację (wiedza, gdzie się znajdujesz lub dowiedzieć się, gdzie są inne rzeczy) i mapowanie (poznanie, co jest wokół ciebie lub zbudowanie mapy środowisko). Za każdym razem, gdy uruchomisz ten skrypt, będzie on zachowywał się inaczej. Zacznie żyć własnym życiem: tworzyć dane, konsumować dane i podejmować decyzje w oparciu o aktualny stan danych. W końcu skryptowi zabraknie danych do przetworzenia i zatrzyma się. Oprócz ogromnego potencjału związanego z podstawową koncepcją sztucznej inteligencji w przeglądarce, znaczenie tego włamania jest dwojakie. Po pierwsze, dodajemy timery do miksu, aby ustawić postMessages w regularnych odstępach czasu. Po drugie, rozwijamy naszą bibliotekę pracowników dalej niż robiliśmy to w przeszłości. Nasza biblioteka będzie wykorzystywać kilka zaawansowanych technik syntaktycznych, których możesz użyć do stworzenia własnej biblioteki roboczej. Robiąc to, rozszerzymy również rdzeń JavaScript, aby stworzyć narzędzie curry. Następnie użyjemy go w naszych wywołaniach setInterval (), przydatnej sztuczki, którą warto zrozumieć, jeśli nie widziałeś wcześniej curry (więcej o tym później). Oto ogólna idea tego, co osiągniemy w tym hacku:

    1. Stwórz pracownika i dodaj EventListeners.

    2. Wygeneruj ruch DOM (znaki).

    3. Uruchom zegary migawek i zarejestruj stan interfejsu użytkownika.

    4. W każdym interwale wysyłaj stan interfejsu użytkownika do pracownika (biblioteka AI).

    5. Wykonaj ciężkie przetwarzanie u pracownika (biblioteka AI).

    6. Przekaż aktualizacje do głównego wątku.

    7. Dokonaj aktualizacji elementów DOM.

    Przegląd ogólny

    Ogólną ideą tego hacka jest wygenerowanie wielu elementów i dołączenie ich do DOM. Te elementy będą elementami ikon, które będziemy nazywać obiektami Thing i dołączymy je do tagu . Ale najpierw skonfigurujmy kilka obiektów do przechowywania naszych danych:

    var things = [], thingMap = {}, elemMap = {};

    Teraz zdefiniujemy klasę Thing, której instancję możemy utworzyć podczas tworzenia nowych rzeczy. Nasza Rzecz zachowa szereg różnych właściwości, które możemy zobaczyć w konstruktorze poniżej. Ponadto, kiedy tworzymy obiekt Thing, wywołamy createThingElem (), co utworzy rzeczywisty element DOM i przypisze referencję przez id do obiektu elemMap.

    var Thing = function(left, top, id) {

    this.id = id;

    this.minDx = ?7; this.maxDx = 7;

    this.minDy = ?7; this.maxDy = 7;

    this.x = this.xOld = left;

    this.y = this.yOld = top;

    this.pxTravelled = 7;

    elemMap[id] = createThingElem(left, top);

    }

    Oto funkcja createThingElem() używana do tworzenia ikon. Widać, że tworzymy element icon i dołączamy go do treści dokumentu.

    var createThingElem = function(left, top) {

    var elem = document.createElement("i");

    elem.innerHTML = "";

    elem.className = "thing user-big";

    document.body.appendChild(elem);

    elem.style.left = this.x;

    elem.style.top = this.y;

    return elem;

    }

    Inicjalizacja rzeczy

    Na początek utworzymy formularz HTML, który pozwoli nam kontrolować włamanie. Stworzymy formularz z jednym polem wyboru umożliwiającym użytkownikowi wybranie liczby rzeczy do utworzenia. Następnie utworzymy przycisk do generowania ruchu.

    < form class="form-horizontal" >

    < fieldset >

    < div class="control-group" >

    < label class="control-label" for="select01" >Number of Characters< /label >

    < div class="controls" >

    < select id="number" >

    < option >choose a value< /option >

    < option >10< /option >

    < option >15< /option >

    < option>20< /option >

    < option >25< /option >

    < option >30< /option >

    < option >35< /option >

    < option >40< /option >

    < option >45< /option >

    < option >50< /option >

    < /select >

    < /div >

    < /div >

    < div class="control-group" >

    < label class="control-label" for="textarea" >< /label >

    < div class="controls" >

    < button id="go" class="btn-large btn-primary" href="#" >

    Generate Movement< /button >

    < /div >

    < /div >

    < /fieldset >

    < /form >

    Teraz, gdy skonfigurowaliśmy nasz interfejs użytkownika, aby zapewnić nam pewne kontrolki, zainicjujmy nasz skrypt, dodając detektor zdarzeń do przycisku Generuj ruch oraz moduł obsługi zdarzeń wywołania zwrotnego o nazwie init (). init () zaakceptuje wartość wybraną w polu wyboru.

    e.preventDefault();

    var num = $('select#number').val();

    var approach = "with worker";

    init(num);

    });

    Po wywołaniu init () nastąpią dwie rzeczy. Po pierwsze, zaczniemy przesuwać nasze ikony rzeczy w oknie roboczym, a po drugie ustawimy kolejny odbiornik kliknięć na innym przycisku, który uruchomi liczniki czasu. Od teraz będziemy nazywać to naszym przyciskiem AI. Dodajmy dodatkowy przycisk:

    < form class="form-horizontal" >

    < fieldset >



    < div class="control-group" >

    < label class="control-label" for="textarea" >< /label >

    < div class="controls" >

    < button id="ai" class="btn-large btn-primary" href="#" >

    Begin AI< /button >

    < /div >

    < /div >

    < /fieldset >

    < /form >

    Teraz możemy rozpocząć przenoszenie Rzeczy, tworząc pętlę while na podstawie liczby przekazanej z pola wyboru. W każdej iteracji utworzymy nową rzecz i dodamy ją do tablicy rzeczy; wywołanie start na każdej rzeczy w tablicy things; i dodaj każdy obiekt Thing do thingMap, ustawiając każdy obiekt rzeczy w tablicy things jako właściwość z jego id jako kluczem w thingMap (więcej o thingMap później):

    init = function(num) {

    var i = ?1;

    while (i++ < num) {

    things[i] = new Thing(400, 300, i);

    things[i].start();

    thingMap[things[i].id] = things[i];

    };



    };

    Metoda start () rozpoczyna ruch ikon rzeczy w DOM:

    Thing.prototype.start = function() {

    var thisElem = elemMap[this.id];

    var thing = this;

    var move = function() {

    thing.x = bounded(thing.x +

    scaledRandomInt(thing.minDx,thing.maxDx),200,600);

    thing.y = bounded(thing.y +

    scaledRandomInt(thing.minDy,thing.maxDy),100,500);

    if (!thing.dead) {

    setTimeout(move, 1);

    }

    thisElem.style.left = thing.x;

    thisElem.style.top = thing.y;

    };

    move();

    }

    Teraz dodamy kolejny moduł obsługi zdarzenia kliknięcia do przycisku AI, który będzie uruchamiał liczniki czasu:

    init = function(num) {



    $('#ai').click(function(e) {

    e.preventDefault();

    var intervals = [];



    });

    };

    W tym momencie jesteśmy gotowi, aby dodać wywołania czasomierza, które w regularnych odstępach czasu wywołują pracowników. Na razie ustawimy timery na 1000 ms lub jedną sekundę. Skonfigurujemy cztery akcje, które będą przetwarzać nasze dane na cztery różne sposoby.

    init = function(num) {

    var i = ?1;

    while (i++ < num) {

    things[i] = new Thing(400, 300, i);

    things[i].start();

    thingMap[things[i].id] = things[i];

    };

    $('#ai').click(function(e) {

    e.preventDefault();

    var intervals = [];

    intervals[0] = window.setInterval(invokeWorker.curry(

    'updatePaths'),1000);

    intervals[1] = window.setInterval(invokeWorker.curry(

    'fireToBelow'),1000),

    intervals[2] = window.setInterval(invokeWorker.curry(

    'rocketToSky'),1000);

    intervals[3] = window.setInterval(invokeWorker.curry(

    'eradicateSlow'),1000);

    });

    };

    Możesz zapytać: "Jaka jest funkcja curry w naszym setIntervals? Co daje nam curry? " Currying używa zamknięcia, aby dać nam możliwość dynamicznego tworzenia funkcji na podstawie przekazanych argumentów. Oto bardzo popularna niestandardowa funkcja curry (), która rozszerza rdzeń JavaScript:

    Function.prototype.curry = function() {

    if (arguments.length<1) {

    return this; //nothing to curry with - return function

    }

    var __method = this;

    var args = toArray(arguments);

    return function() {

    return __method.apply(this, args.concat(

    toArray(arguments)));

    }

    }

    Currying zapewnia wygodny sposób pisania mniejszej ilości kodu i umożliwiania jego ponownego wykorzystania w całej aplikacji. Możemy to lepiej zrozumieć, patrząc na funkcję invokeWorker():

    var invokeWorker = function(action) {

    // console.log(things)

    worker.postMessage({

    'action': action,

    'things': things

    });

    }

    Rozszerzając rdzeń JavaScript, możemy wywołać curry na istniejącej funkcji i przekazać jej ciąg. Ciąg staje się odniesieniem do metody w naszej bibliotece roboczej (więcej na ten temat później; na razie po prostu wiedz, że parametr akcji invokeWorker jest ustawiony na zmienną łańcuchową, a następnie ustawiany jako wartość właściwości action naszego niestandardowego obiektu, wysyłamy pocztą () do naszego pracownika). W tym przykładzie nie musimy serializować naszego obiektu, ponieważ tworzymy go ręcznie za pomocą składni literału obiektu.

    {

    'action': action,

    'things': things

    }

    W tym momencie mamy elementy ikon poruszające się po ekranie, z których każdy ma powiązany obiekt konfiguracyjny. Tak więc, gdy użytkownik kliknie moduł obsługi zdarzeń, aby rozpocząć sztuczną inteligencję (lub uruchomić liczniki czasu), skrypt zaczyna sondować informacje o tych elementach, takie jak ich współrzędne x i y, i wysyła dane do biblioteki pracowników sieci przetwarzanie. Kiedy biblioteka otrzyma dane, wykona obliczenia na danych przy użyciu funkcji akcji które zostało przekazane w ramach naszej funkcji curry.

    Biblioteka pracowników

    Jak omówiliśmy wcześniej, funkcja curried, która jest wywoływana przez nasze liczniki setInterval, przekazuje odniesienie do akcji, którą chcemy wykonać w naszej bibliotece roboczej. Robi to poprzez utworzenie obiektu w invokeWorker () i przekazanie go do API postMessage () naszego pracownika.

    var things;

    var updates;

    // UTILITIES

    var scaledRandomInt = function(max, min) {

    return Math.round(min + Math.random()*(max-min));

    }

    var getDistance = function(x1,x2,y1,y2) {

    return Math.sqrt(Math.pow(Math.abs(x1-x2),2) + Math.pow(

    Math.abs(y1-y2),2));

    }

    // ACTION Methods

    var Actions = {

    fireToBelow: function(){

    var highest = things.sort(function(a, b){

    return a.y - b.y

    });

    updates = {};

    updates.action = 'fireToBelow';

    updates.id = highest[0].id;

    updates.minDy = ?2;

    updates.maxDy = 3;

    updates.symbol = '';

    updates.className = 'thing user-fire';

    postMessage(updates);

    },

    rocketToSky: function(){

    var lowest = things.sort(function(a, b){

    return b.y - a.y

    });

    updates = {};

    updates.action = 'rocketToSky';

    updates.id = lowest[0].id;

    updates.minDy = ?3;

    updates.maxDy = 2;

    updates.symbol = '';

    updates.className = 'thing user-plane';

    postMessage(updates);

    },

    eradicateSlowest: function(){

    var slowest = things.sort(function(a, b){

    return a.pxTravelled - b.pxTravelled

    });

    updates = {};

    updates.action = 'eradicateSlowest';

    updates.id = slowest[0].id;

    updates.kill = true;

    postMessage(updates);

    },

    updatePaths: function(){

    for (var i = things.length-1; i; i--) {

    var t = things[i];

    t.pxTravelled += getDistance(t.xOld, t.x, t.yOld, t.y);

    t.xOld = t.x; t.yOld = t.y;

    }

    }

    }

    onmessage = function(e){

    things = e.data.things;

    Actions[e.data.action]();

    }

    Teraz, gdy nasza biblioteka robocza przeprowadziła niezbędne przetwarzanie, przesyła dane z powrotem do głównego wątku, który nasłuchiwał nowych wiadomości. Wszelkie nowe komunikaty wyzwalają moduł obsługi zdarzeń onmessage (), który dokona aktualizacji interfejsu użytkownika. Jak widać, wiele metod w naszej bibliotece roboczej przetwarza dane poprzez manipulacje tablicami. Te manipulacje mogą być bardzo kosztowne, zwłaszcza gdy zwiększamy rozmiar ładunku danych. Jednym z głównych punktów, które należy usunąć z tego hacka, jest to, że ta biblioteka jest zorganizowana tak, aby rosła w bardzo czysty i łatwy w utrzymaniu sposób. Po prostu dodaj dodatkową metodę, tworząc nową właściwość ustawioną na wartość funkcji. Wreszcie, ostatni cukier syntaktyczny, którego użyjemy do uporządkowania i uporządkowania tej biblioteki, ma miejsce w słuchaczu onmessage (). Tutaj znajduje się odniesienie do obiektu Actions, który możemy nazwać klasą Singleton JavaScript. W JavaScript możemy tworzyć pojedyncze lub klasy, które są tworzone tylko raz, używając prostej skojarzonej tablicy par nazwa-wartość. Wzór wygląda następująco:

    var Actions = {

    fireToBelow: function(){



    },

    rocketToSky: function(){



    },

    eradicateSlowest: function(){



    },

    updatePaths: function(){



    }

    }

    Jest to ten sam wzorzec, co prosty obiekt JSON. Teraz musimy użyć składni nawiasów tablicowych, aby wywołać wybraną metodę z klasy Singleton. Pamiętaj, że nazwa funkcji została przekazana z głównego wątku. Teraz możemy zarządzać wywołaniami dowolnej metody w naszej bibliotece za pomocą tylko jednego modułu obsługi onmessage().

    onmessage = function(e){

    things = e.data.things;

    Actions[e.data.action]();

    }

    Użytkowanie w świecie rzeczywistym

    Jak wspomniałem we wstępie do tego hacka, podstawowe koncepcje tego prostego skryptu niosą ze sobą ogromny potencjał do wykorzystania w świecie rzeczywistym. Oto kilka pomysłów na początek:

    •  Analiza offline •  CoBrowse, rozwiązania do śledzenia użytkowników

    •  Przetwarzanie obrazu po stronie klienta

    •  XMLHttpRequests w tle

    •  Odczyt / zapis w tle do pamięci lokalnej

    Omówimy niektóre z nich w nadchodzących hackach. Teraz idź i zbuduj własną bibliotekę pracowników, która może samodzielnie podejmować decyzje.

    Hack No.68

    Przetwarzaj dane obrazu za pomocą manipulacji pikselami u dedykowanego pracownika

    Jednym z najbardziej praktycznych zastosowań pracowników sieci jest przetwarzanie danych obrazu po stronie klienta bez konieczności przekazywania danych tam i z powrotem ze zdalnego serwera. Manipulowanie pikselami to powszechny sposób dodawania do obrazów efektów podobnych do filtrów. Ponieważ masz dostęp do zdarzeń ujawnionych przez natywny interfejs API przeglądarki, możesz zastosować te zdarzenia na podstawie danych wejściowych użytkownika. W tym hacku zastosujemy filtr skali szarości do obrazu logo HTML5. Zastosujemy konfigurację filtru na podstawie danych o lokalizacji i zainicjowaną przez natywne zdarzenie przeglądarki. Dane lokalizacji, których użyjemy, będą pochodzić ze współrzędnej x kursora myszy, a zdarzeniem, którego użyjemy, będzie zdarzenie najechania myszą. Gdy najedziemy kursorem na logo HTML5 od lewej do prawej, liczba będzie mniejsza niż w przypadku najechania kursorem myszy na obraz od prawej do lewej. W takim przypadku przekażemy dane pracownikowi sieciowemu w celu przetworzenia obrazu. Filtr, który utworzymy i zastosujemy, po prostu usunie cały kolor z wyjątkiem czerni i bieli (w istocie będzie to filtr w skali szarości). W rezultacie obraz stanie się jaśniejszy szary, gdy wejdziemy najechanie kursorem z lewej strony obrazu, a ciemniejszy szary, gdy wprowadzimy kursor myszy z prawej strony Najpierw użyjmy jQuery, aby zastosować nasz detektor zdarzeń do obrazu, przechwyć współrzędne i przekaż dane zdarzenia do funkcji process():

    $ (". hover-img"). on ("mouseover", function (e) {

    var x = e.pageX - this.offsetLeft;

    var y = e.pageY - this.offsetTop;

    console.log ("X:" + x + "Y:" + y);

    proces (this, x, y);

    });

    Teraz zbudujemy naszą funkcję przetwarzania, która zastosuje nasz filtr do danych obrazu. Nasza funkcja zaakceptuje obraz przechwycony w zdarzeniu najechania myszą oraz współrzędne x i y.

    function process(img, x, y) {

    //process the img based on x,y

    }

    Następnie musimy utworzyć w pamięci płótno o takim samym rozmiarze, jak obraz, który przekazaliśmy do przechwytywanego obrazu:

    var canvas = document.createElement("canvas");

    canvas.width = img.width;

    canvas.height = img.height;

    Teraz skopiujemy obraz do obszaru roboczego, a następnie wyodrębnimy jego piksele:

    var context = canvas.getContext("2d");

    context.drawImage(img, 0, 0);

    var pixels = context.getImageData(0,0,img.width,img.height);

    Here we are sending the pixels to a worker thread:

    var worker = new Worker("javascripts/greyscale.js");

    var obj = {

    pixels: pixels,

    x:x,

    y:y

    }

    worker.postMessage(obj); // Copy and send pixels

    W tym momencie musimy zarejestrować osobę obsługującą, aby uzyskać odpowiedź pracownika. Kiedy otrzymamy odpowiedź, utworzymy zmienną lokalną, której użyjemy do umieszczenia obrazu z powrotem w obiekcie kontekstu. W tym celu użyjemy metody putImageData () i przekażemy współrzędne xiy w celu przesunięcia danych nowego obrazu. W tym przypadku chcemy umieścić nowy obraz w tym samym miejscu, z którego pobraliśmy oryginalne dane, więc użyjemy 0 0. Na koniec użyjemy toDataURL (), aby dodać dane z powrotem do atrybutu src naszego obrazu. Kanwa zawiera metodę toDataURL (), która pobierze dane z kanwy i utworzy ciąg, który można ustawić na właściwość src obrazu. Dołączenie tego obrazu gdzieś w dokumencie spowoduje wyświetlenie danych jako obrazu.

    worker.onmessage = function(e) {

    if (typeof e.data === "string") {

    console.log("Worker: " + e.data);

    return; }

    var new_pixels = e.data.pixels; // Pixels from worker

    context.putImageData(new_pixels, 0, 0);

    img.src = canvas.toDataURL(); // And then to the img

    }

    Być może zauważyłeś technikę debugowania, której możemy użyć. Ponieważ nie możemy używać konsolowego interfejsu API w ramach procesu roboczego, musimy sprawdzić typ danych zwracanej wiadomości w programie obsługi zdarzeń nasłuchującym wiadomości w głównym wątku. Jeśli wiadomość jest typu string, zakładamy błąd i rejestrujemy go.

    if (typeof e.data === "string") {

    console.log("Worker: " + e.data);

    return; }

    Oto gotowy produkt:

    function process(img, x, y) {

    // Create an offscreen < canvas > the same size as the image

    var canvas = document.createElement("canvas");

    canvas.width = img.width;

    canvas.height = img.height;

    // Copy the image into the canvas, then extract its pixels

    var context = canvas.getContext("2d");

    context.drawImage(img, 0, 0);

    var pixels = context.getImageData(0,0,img.width,img.height);

    var worker = new Worker("javascripts/greyscale.js");

    var obj = {

    pixels: pixels,

    x:x,

    y:y

    }

    worker.postMessage(obj);

    worker.onmessage = function(e) {

    if (typeof e.data === "string") {

    console.log("Worker: " + e.data);

    return; }

    var new_pixels = e.data.pixels;

    context.putImageData(new_pixels, 0, 0);

    img.src = canvas.toDataURL();

    }

    }

    Na koniec musimy stworzyć nasz filtr skali szarości w naszym skrypcie roboczym. Tutaj wywołamy funkcję filter(), która przetworzy dane obrazu, usuwając dane niezbędne do zwrócenia szarego obrazu. Dynamiczna magia występuje w zmiennej skali szarości, która mnoży położenie współrzędnej x w formule, aby zwrócić dane obrazu, które zawierają tylko odcienie szarości.

    onmessage = function (e) {postMessage (filter (e.data))};

    function filter(imgd) {

    var pix = imgd.pixels.data;

    var xcord = imgd.x/1000;

    var ycord = imgd.y/1000;

    for (var i = 0, n = pix.length; i < n; i += 4) {

    var grayscale = pix[i] * xcord + pix[i+1] * .59

    + pix[i+2] * .11;

    pix[i] = grayscale; // red

    pix[i+1] = grayscale; // green

    pix[i+2] = grayscale; // blue

    }

    imgd['pixels'].data = pix;

    return imgd;

    }

    Hack No.69

    Użyj skryptów importu, aby wykonać żądania JSONP na Twitterze

    Interfejs API WebWorker umożliwia importowanie bibliotek innych firm lub bibliotek zewnętrznych za pomocą metody importScripts (). JSONP lub JSON z dopełnieniem to szeroko stosowana technika pobierania kodu JavaScript z innych domen bez konieczności przestrzegania tej samej zasady przeglądarki. W tym hacku wywołamy interfejs API wyszukiwania Twittera w celu uzyskania ostatnich 100 tweetów zawierających słowo kluczowe html5 w treści. Wykorzystamy dostępną dla nas funkcję importScript () w kontekście pracownika. Jak w przypadku każdego dedykowanego pracownika, pierwszą rzeczą, jaką zrobimy, jest utworzenie nowego pracownika i wskazanie pliku zewnętrznego:

    var worker = new Worker("javascripts/jsonp-worker.js");

    Następnie skonfigurujemy nasłuchiwanie wszelkich wiadomości przekazywanych z powrotem do naszego głównego wątku. W naszym odbiorniku zapętlimy odpowiedź i zbudujemy div dla każdego wyniku. W trakcie dodamy również zdjęcie profilowe i nazwę użytkownika Twittera, który opublikował tweet.

    var worker = new Worker("javascripts/jsonp-worker.js");

    worker.onmessage = function(e) {

    console.log(e.data);

    var res = e.data;

    for ( key in res.results){

    var item = res.results[key];

    var img = $('').attr('src',item.profile_image_url);

    var div = $('
    ').append(img);

    var text= $('
    ').html($.trim(item.text));

    div.append(text);

    div.attr('class','tweet');

    $('#listDiv').append(item.from_user);

    $('#list').append(div);

    }

    }

    Ale wyprzedzamy siebie, więc najpierw wyślijmy prośbę do Twittera z poziomu naszego pracownika. Nasz skrypt roboczy jest dość prosty. Musimy skonfigurować funkcję zwrotną, którą przekażemy w ramach naszego żądania do Twittera. W ramach naszego wywołania zwrotnego po prostu przekażemy obiekt z powrotem do głównego wątku, aby zbudować interfejs użytkownika. Później zbadamy niektóre możliwości przetwarzania danych z Twittera, ale na razie po prostu przekażemy pełny obiekt z powrotem do głównego wątku.

    var callback = function (obj) {

    if (obj.hasOwnProperty("results")) {

    // process the data

    postMessage(obj);

    } else {

    postMessage("No results.");

    }

    };

    Zgłoszenie żądania jest proste i obejmuje połączenie JSONP i importScripts ().

    JSONP

    JSONP lub JSON z dopełnieniem to technika używana do obejścia tej samej zasady pochodzenia przeglądarki w celu pobierania kodu JavaScript z innej domeny. Serwer API JSONP odczyta parametr żądania wywołania zwrotnego i zawinie odpowiedź w formacie JSON w ramach tej funkcji. Technika następnie wykorzystuje sposób, w jaki przeglądarka interpretuje i wykonuje JavaScript, gdy tag skryptu jest generowany dynamicznie i dołączany do DOM. Jak wspomniałem w innych hackach dotyczących pracowników sieci Web, nasz wątek roboczy jest ograniczony, ponieważ nie mamy dostępu do DOM, ale mamy dostęp do specjalnej funkcji importScripts (). Zadzwońmy więc do interfejsu API Twittera, aby uzyskać 100 najnowszych tweetów z html5 jako zapytaniem. Zwróć uwagę na parametr callback i odwołanie do funkcji callback.

    importScripts ("http://search.twitter.com/search.json?

    q = html5 & rpp = 100 & since_id = 1 & callback = callback ");

    Oto ostateczny kod pracownika sieci:

    var callback = function (obj) {

    if (obj.hasOwnProperty("results")) {

    // process data

    postMessage(obj);

    } else {

    postMessage("No results.");

    }

    };

    importScripts("http://search.twitter.com/search.json?

    q=html5&rpp=100&since_id=1&callback=callback");

    Jak wspomniałem wcześniej, jest to bardzo prosty przykład. Takie podejście może być bardzo wydajne w przypadku przetwarzania danych API w ramach pracownika internetowego. Często programiści będą chcieli zmienić format danych w pliku roboczym, a następnie przekazać złożone lub nawet mniejsze części oryginalnych danych z powrotem do głównego wątku. Teraz Ty też możesz.

    Hack No.70

    Połącz się ze współdzielonymi pracownikami jednocześnie z wielu okien przeglądarki

    Dedykowani pracownicy sieciowi są bezpośrednio powiązani z ich odpowiednimi skryptami startowymi, ale współużytkowani pracownicy sieciowi pozwalają dowolnej liczbie kontekstów okien przeglądarki na jednoczesną komunikację z jednym pracownikiem. Jak zobaczysz w tym hacku, współdzieleni pracownicy implementują nieco inny interfejs API, ale ogólnie koncepcje są bardzo podobne. Podobnie jak dedykowani pracownicy, aby utworzyć współużytkowanego pracownika WWW, przekazujesz nazwę pliku JavaScript do swojej instancji Worker, z tym wyjątkiem, że tym razem używasz obiektu SharedWorker. W przeciwieństwie do dedykowanych pracowników sieci Web, pracownicy współużytkowani wprowadzają koncepcję obiektu portu, który musi zostać wyznaczony wraz z dołączoną procedurą obsługi zdarzeń komunikatów. Następnie wywołujemy metodę start () portu. I wreszcie, jesteśmy gotowi użyć naszego standardowego postMessage ():

    var worker = new SharedWorker('javascripts/shared-simple.js');

    var log = document.getElementById('log');

    worker.port.addEventListener('message', function(e) {

    log.textContent += '\n' + e.data;

    if (e.data.charAt(0) == '#'){

    document.body.style.background = e.data;

    }

    }, false);

    worker.port.start();

    Ponieważ wszystkie istniejące skrypty strony, a nawet skrypty w innych oknach, mogą komunikować się ze współużytkowanym pracownikiem sieciowym, utworzymy trzy elementy iframe, aby zademonstrować komunikację w kontekstach okien przeglądarki:

    < pre id="log" >Log:< /pre >

    < iframe src="/shared-simple-inner.html" >< /iframe >

    < iframe src="/shared-simple-inner2.html" >< /iframe >

    < iframe src="/shared-simple-inner3.html" >< /iframe >

    W każdym dokumencie ładowanym do elementów iframe utworzymy wystąpienie nowych obiektów SharedWorker, które będą wskazywać na ten sam zewnętrzny skrypt. Program obsługi zdarzeń onmessage będzie oczekiwał w odpowiedzi dwóch pozycji: liczby utrzymywanej przez licznik z wątkiem roboczym oraz losowo wygenerowanego koloru, który zostanie ustawiony jako kolor tła dokumentu, który zrodził pracownika.

    < !DOCTYPE HTML >

    < title >HTML5 Hacks: Shared Worker< /title >

    < pre id=log >Log:< /pre >

    < script >

    var worker = new SharedWorker('javascripts/shared-simple.js');

    var log = document.getElementById('log');

    worker.port.onmessage = function(e) {

    log.textContent += '\n' + e.data;

    if (e.data.charAt(0) == '#'){

    document.body.style.background = e.data;

    }

    }

    < /script >

    W ramach naszego pracownika będziemy utrzymywać licznik, który będzie się zwiększał wraz z każdym podłączonym klientem. Numer jest wysyłany z powrotem do głównego wątku, który spowodował powstanie tej konkretnej instancji pracownika. Następnie wygenerujemy losowy kolor i prześlemy go z powrotem w tym samym kontekście.

    var count=0;

    onconnect = function(e) {

    count++;

    var port = e.ports[0];

    port.postMessage('Established connection: ' + count);

    var randomColor = '#'+(0x1000000+(Math.random())*0xffffff).to

    String(16).substr(1,6);

    port.postMessage(randomColor);

    }

    Teraz, gdy odświeżymy stronę, zobaczymy, że cztery niezależne skrypty startowe otrzymują asynchroniczne odpowiedzi z wątku roboczego. Nie tylko tła są generowane losowo, ale każdy skrypt odradzający łączy się za każdym razem w nieco innej kolejności.

    Hack No.71

    Użyj zdalnego serwera WebSocket firmy Kaazing, dla echo prostych wiadomości z przeglądarki

    Serwer echo to oparty na sieci Web serwer gniazd stworzony przez firmę Kaazing i hostowany w witrynie websocket.org. Demonstruje możliwości protokołu WebSocket poprzez echo komunikatów wysyłanych z przeglądarki. Otwórzmy plik HTML i zacznijmy od utworzenia podstawowej klasy WebSocketDemo w języku JavaScript. Ponieważ JavaScript nie jest oparty na klasach, będziemy postępować zgodnie z bardzo popularnym wzorcem pseudoklas do zarządzania naszym kodem. Podstawową strukturę naszych tagów JavaScript przedstawiłem w poniższym kodzie:

    < script language="javascript" type="text/javascript" >

    WebSocketDemo = function(){

    return {

    // public methods go here.

    }

    }();

    WebSocketDemo.init("ws://echo.websocket.org/");

    < /script >

    Musimy również dodać znacznik DIV z id = "wyjście". Będzie to miejsce, w którym będziemy rejestrować nasze wiadomości.

    < h2 > Test WebSocket < /h2 >

    < div id = "output" > < /div >

    Klasa WebSocketDemo ma cztery publiczne metody-init(), onOpen(), onClose() i onMessage() - oraz jedna właściwość publiczna, ws. Właściwość ws będzie przechowywać instancję naszego nowego obiektu WebSocket. Przyjrzyjmy się teraz metodzie init(). W WebSocketDemo.init() jako jedyny parametr podajemy adres URL. Następnie jest przekazywany do tworzenia wystąpienia nowego WebSocket. Zauważ, że nasz adres URL używa przedrostka ws: //. Protokół WebSocket definiuje ws: // dla podstawowego połączenia z gniazdem internetowym i wss: // dla bezpiecznego połączenia z gniazdem internetowym. W przypadku tego hacka będziemy trzymać się podstaw i używać połączenia ws: //.

    WebSocketDemo = function(){

    return {

    ws: null,

    init: function(url){

    this.ws = new WebSocket(url);

    }

    }

    }();

    WebSocketDemo.init("ws://echo.websocket.org/");

    Teraz stwórzmy metodę o nazwie onOpen(), aby opakować nasze wywołanie metody send() i nasłuchiwać zdarzenia uruchamianego po nawiązaniu połączenia:

    WebSocketDemo = function(){

    return {

    ws: null,

    init: function(url){

    this.ws = new WebSocket(url);

    this.onOpen();

    },

    onOpen: function(){

    this.ws.onopen = function(evt) {

    console.log('CONNECTED: ' + evt.type);

    WebSocketDemo.ws.send('html5 hacks');

    };

    }

    }

    }();

    WebSocketDemo.init ("ws: //echo.websocket.org/");

    W ramach metody onOpen () zawarliśmy również console.log, który będzie rejestrował typ zdarzenia. Aby nawiązać połączenie WebSocket, klient i serwer dokonują aktualizacji z protokołu HTTP do protokołu WebSocket podczas wstępnego uzgadniania. Istnieje kilka mechanizmów uzgadniania, ale jeśli nie piszesz implementacji gniazd po stronie serwera, interfejs API WebSocket JavaScript zaimplementowany w przeglądarce usunie te szczegóły z dala od Ciebie. Żądanie uzgadniania jest tak sformatowane (pokazane są tylko podstawowe nagłówki):

    GET /demo HTTP/1.1

    Host: echo.websocket.org

    Connection: Upgrade

    Upgrade: WebSocket

    Origin: null

    Oto odpowiedź typu handshake (ponownie pokazane są tylko podstawowe nagłówki):

    HTTP/1.1 101 WebSocket Protocol Handshake

    Upgrade: WebSocket

    Connection: Upgrade

    Server: Kaazing Gateway

    Korzystając z narzędzi programistycznych Google Chrome, możesz sprawdzić interakcję klient / serwer. Kliknij kartę Sieć i wybierz "echo.websocket.org" po lewej stronie, aby wyświetlić informacje w nagłówku żądania i odpowiedzi. Zauważysz w nagłówkach żądań, że istnieje nagłówek Connection: Upgrade. Zobaczysz również, że typ jest wskazany w nagłówku Upgrade, a hostem odbierającym jest echo.websocket.org. W odpowiedzi widzimy, że został zwrócony nagłówek Upgrade, a odpowiadająca mu wartość to WebSocket. To informuje przeglądarkę klienta, że żądany typ uaktualnienia był dostępny i trwałe połączenie zostało otwarte. Teraz rozwińmy nieco nasz kod i zacznijmy dodawać kilka innych metod publicznych, które ujawnią więcej zdarzeń związanych z połączeniami, skutecznie demonstrując wewnętrzne funkcje komunikacji gniazda: onClose() i onMessage().

    onClose: function(){

    this.ws.onclose = function(evt) {

    console.log('CLOSED: ' + ': ' + evt.type);

    };

    },

    onMessage: function(msg){

    this.ws.onmessage = function(evt) {

    console.log('RESPONSE: ' + ': ' + evt.data);

    WebSocketDemo.ws.close();

    };

    }

    }

    }();

    A w naszym init() wykonajmy nasze nowe metody:

    nit: function(url){

    this.ws = new WebSocket(url);

    this.onOpen();

    this.onMessage();

    this.onClose();

    },

    Teraz, jeśli odświeżymy przeglądarkę, powinniśmy zobaczyć nowe dzienniki konsoli pochodzące z naszych nowych metod zdarzeń. Wywołanie send w ramach onOpen() przekazuje komunikat "html5 hacks" do zdalnego serwera echa, a serwer wysyła odpowiedź. Zdarzenie onMessage() rejestruje komunikat i wywołuje onClose. W najnowszej wersji Chrome w wersji Canary otrzymaliśmy również nową kartę w narzędziach deweloperskich, która pozwala nam przeglądać ruch przesyłany między przeglądarką a zdalnym serwerem. Aby skorzystać z tej możliwości, najpierw musisz getCanary. Teraz wykonaj następujące kroki:

    1. Przejdź do właśnie utworzonego pliku index.html.

    2. Włącz narzędzia Chrome Developer Tools lub naciśnij Apple + Shift + I (Mac OS X) lub Ctrl-Shift-I (Windows i Linux).

    3. Kliknij kartę Sieć i kliknij opcję Gniazda sieciowe, tak jak to zrobiliśmy w poprzednim przykładzie.

    4. Wybierz "echo.websocket.org".

    5. Wybierz kartę Ramki WebSocket.

    Podane informacje są bardzo przydatne i zapobiegają konieczności instalowania narzędzia innej firmy:

    Number : to jest licznik, aby zademonstrować kolejność komunikatów.

    Arrow: To jest kierunek wiadomości.

    Timestamp: jest to godzina zainicjowania lub odebrania wiadomości.

    Op-code: Kody operacyjne są podzielone na trzy kategorie: kontynuacja, brak kontroli i kontrola. Kody operacji kontynuacji i braku kontroli wskazują, że komunikaty użytkownika i ramki sterujące są używane do konfiguracji samego protokołu: 1 oznacza wiadomość tekstową, a 8 oznacza zamknięte połączenie.

    Length: jest to liczba znaków w ładunku.

    Contents : to są rzeczywiste dane w ładunku WebSocket.

    Dla jasności podsumujmy interakcję:

    1. Przeglądarka klienta tworzy instancję nowego obiektu WebSocket i przekazuje mu adres URL przy użyciu protokołu ws: //.

    2. Następnie przeglądarka klienta wysyła żądanie HTTP do zdalnego serwera z żądaniem aktualizacji połączenia WebSocket.

    3. Serwer odpowiada nagłówkami odpowiedzi wskazującymi Połączenie: Upgrade typu Upgrade: WebSocket.

    4. To uruchamia zdarzenie onopen.

    5. Za pomocą metody onOpen () przeglądarka klienta wywołuje metodę send() i przekazuje ładunek. 6. Serwer odpowiada echem tego samego ładunku, wyzwalając zdarzenie onmessage.

    7. Metoda zdarzenia onMessage () rejestruje ładunek w konsoli klienta, a następnie wywołuje metodę close().

    8. Metoda zdarzenia onClose () jest wyzwalana i rejestruje "ZAMKNIĘTE" i typ zdarzenia w konsoli

    Oto ostateczny kod w całości. To dopiero początek dobrze zorganizowanej biblioteki JavaScript WebSocket. Weź to i zacznij własne hacki!

    < html >

    < head >

    < meta charset="utf-8" / >

    < title >WebSocket Test< /title >

    < script language="javascript" type="text/javascript" >

    WebSocketDemo = function(){

    return {

    ws: null,

    init: function(url){

    this.ws = new WebSocket(url);

    this.onOpen();

    this.onMessage();

    this.onClose();

    },

    doSend: function(msg){

    this.ws.send = function(evt) {

    console.log(evt.timeStamp)

    };

    },

    onOpen: function(){

    this.ws.onopen = function(evt) {

    console.log('CONNECTED: ' + evt.type);

    WebSocketDemo.ws.send('html5 hacks');

    };

    },

    onClose: function(){

    this.ws.onclose = function(evt) {

    console.log('CLOSED: ' + ': ' + evt.type);

    };

    },

    onMessage: function(msg){

    this.ws.onmessage = function(evt) {

    console.log('RESPONSE: ' + ': ' + evt.data);

    WebSocketDemo.ws.close();

    };

    }

    }

    }();

    WebSocketDemo.init("ws://echo.websocket.org/");

    < /script >

    < /head >

    < body >

    < h2 >WebSocket Test< /h2 >

    < div id="output" >< /div >

    < /body >

    < /html >

    Hack No.72

    Zbuduj niesamowicie szybki serwer WebSocket za pomocą Node.js i modułu ws

    Moduł ws Node.js to łatwa w użyciu, niezwykle szybka i aktualna implementacja gniazda sieciowego, której można użyć do szybkiego skonfigurowania i uruchomienia gniazd internetowych. Jest również dostarczany z wscat, narzędziem poleceń, które może działać jako klient lub serwer. W tym hacku zbadamy najszybszy serwer WebSocket, jaki udało mi się znaleźć. Odkryłem moduł ws dla Node.js. Tak się składa, że ws jest nie tylko niesamowicie szybki, ale także dość prosty w konfiguracji. Prostota jego implementacji sprawi, że ten hack będzie idealnym wprowadzeniem do gniazd sieciowych. Moduł ws jest zgodny z aktualnymi wersjami roboczymi protokołu HyBi i może wysyłać i odbierać tablice o określonych typach (ArrayBuffer, Float32Array itp.) Jako dane binarne. Więc chociaż może to być proste, to nie jest zabawka.

    ws jest również dostarczany z ładnym klientem wiersza poleceń o nazwie wscat, który daje nam narzędzie do tworzenia i odbierania żądań bez korzystania z przeglądarki.

    Instalowanie Node.js.

    W sieci jest już mnóstwo świetnej dokumentacji dotyczącej instalowania i uruchamiania Node.js, więc nie będę jej tutaj ponownie tworzyć. Dobry punkt wyjścia można znaleźć na GitHub. Możesz także po prostu kliknąć przycisk Instaluj na środku ekranu na stronie nodejs.org.

    Używanie klienta wscat do wywoływania serwera Kaazing Echo.

    Po skonfigurowaniu i zainstalowaniu Node.js powinieneś być w stanie otworzyć wiersz poleceń i użyć Node Package Manager (NPM), aby zainstalować moduł ws:

    $ npm install -g ws

    Ponieważ jest to biblioteka gniazd i klient wiersza poleceń, dodamy parametr -g, aby zainstalować skrypty globalnie. W ten sposób możemy często korzystać z tej biblioteki i do wielu różnych zastosowań. Teraz patrz, jak NPM pobiera i instaluje moduł ws oraz wszystkie niezbędne elementy zależności. W tym momencie powinniśmy być w stanie natychmiast użyć wscat do wysłania żądania do zdalnego serwera echa hostowanego przez Kaazing:

    $ wscat -c ws: //echo.websocket.org

    Mamy to. Nasze narzędzie wiersza poleceń jest gotowe i działa. Możemy działać jak przeglądarka i wysyłać wiadomości do zdalnego serwera sieciowego.

    Tworzenie prostego serwera i łączenie się z nim za pomocą wscat

    Teraz, gdy nasze narzędzia są już gotowe, zbudujmy własny prosty serwer z gniazdami. Przejdź do katalogu projektu, otwórz plik i nadaj mu nazwę server.js:

    $ cd /your-app-directory

    $ touch server.js

    To był jeden ze sposobów tworzenia pliku. Możesz bardzo łatwo zrobić to na swój własny sposób. Jeśli wolisz korzystać z GUI swojego systemu operacyjnego w celu uzyskania dostępu do systemu plików, przejdź do pustego katalogu, otwórz prosty edytor tekstu i utwórz plik o nazwie server.js. W pliku server.js użyj require (), aby dołączyć bibliotekę ws i utworzyć instancję nowego serwera WebSocketServer działającego na porcie 8080:

    var WebSocketServer = require('ws').Server

    , wss = new WebSocketServer({port: 8080});

    Teraz możemy użyć metody on () do nasłuchiwania zdarzenia połączenia. Po uruchomieniu zdarzenia połączenia wywoływana jest funkcja zwrotna zawierająca inną zagnieżdżoną funkcję, która nasłuchuje zdarzenia komunikatu od wszystkich połączonych klientów. Następnie uruchamiamy metodę send (), przekazując ładunek z ciągiem "'I am a message sent from a ws server".

    wss.on('connection', function(ws) {

    ws.on('message', function(message) {

    console.log('received: %s', message);

    });

    ws.send('I am a message sent from a ws server');

    });

    Teraz zapisujemy plik i uruchamiamy serwer:

    $ node server.js

    W innym oknie terminala możemy użyć klienta wscat, aby uzyskać dostęp do naszego własnego serwera działającego na porcie 8080. I otrzymujemy naszą wiadomość.

    $ wscat -c ws://localhost:8080 -p 8

    connected (press CTRL+C to quit)

    < I am a message sent from a ws server

    >

    I wreszcie możemy wysłać wiadomość z powrotem na serwer, ręcznie wpisując testowanie w interfejsie wiersza poleceń. Teraz przejdź z powrotem do innej karty, na której działa serwer, aby zobaczyć wiadomość od klienta.

    Serwer gniazda nasłuchuje i rejestruje testy:

    $ node server.js

    Wiadomość została odebrana od klienta WS: testing

    Tworzenie prostego klienta

    Zamiast wchodzić w interakcje z naszym serwerem WebSocket za pośrednictwem interfejsu wiersza poleceń wscat, napiszmy skrypt, który zajmie się tą interakcją za nas. Najpierw wymagaj biblioteki ws i utwórz nową instancję WebSocket. Następnie skonfiguruj dwie procedury obsługi oneevent: jedną do nasłuchiwania zdarzenia open dla połączenia i drugą do nasłuchiwania wszelkich komunikatów przychodzących. Użyjemy poręcznego serwera echo echo.websocket.org, aby odzwierciedlić odpowiedź na nasze żądanie.

    var WebSocket = require ('ws')

    , ws = new WebSocket ('ws: //echo.websocket.org ');

    ws.on ('open', function () {

    ws.send ('Jestem otwartym zdarzeniem z klienta ws');

    });

    ws.on ('wiadomość', funkcja (wiadomość) {

    console.log ("otrzymano:% s", wiadomość);

    });

    Zacznijmy klienta:

    $ node client.js

    otrzymano: Jestem otwartym WYDARZENIEM od klienta ws

    W otwartym zdarzeniu komunikat "Jestem otwartym WYDARZENIEM od klienta ws" jest wysyłany do zdalnego serwera echa. Serwer zdalny zwraca następnie wiadomość. Klient nasłuchuje komunikatów i rejestruje odpowiedź z komunikatem "Otrzymano: Jestem otwartym ZDARZENIEM od klienta ws".

    Masz to. Masz teraz doskonały przykład serwera WebSocket działającego lokalnie oraz interfejs wiersza poleceń do tworzenia i nasłuchiwania komunikatów gniazda sieciowego bez przeglądarki.

    Hack No.73

    Zbuduj termometr darowizn za pomocą gniazd internetowych, interfejsu API Pusher i PHP

    Termometry darowizn są używane podczas wielu imprez charytatywnych i witryn darowizn, ale zwykle aktualizują się tylko po odświeżeniu strony. Aktualizowanie tych termometrów w czasie rzeczywistym jest teraz łatwiejsze niż kiedykolwiek, gdy ktoś przekaże darowiznę, korzystając z gniazd internetowych HTML5. Obecnie dostępnych jest wiele usług i rozwiązań w czasie rzeczywistym. W przypadku termometru darowizny użyjemy Pushera, który korzysta z gniazd internetowych HTML5, aby dodać do widżetu funkcjonalność w czasie rzeczywistym. Ta funkcjonalność w czasie rzeczywistym nie tylko zapewni natychmiastową poprawną wartość darowizny, ale także zwiększy poziom ekscytacji, który może skutkować "lepkością", która przykuwa uwagę użytkownika.

    Progresywne ulepszanie

    Dostępność jest zawsze ważna, ale potencjalnie bardziej ważna w przypadku organizacji charytatywnych, dlatego ważne jest, aby widżet termometru darowizny przynajmniej wyświetlał coś, nawet jeśli JavaScript nie jest włączony. Dlatego będziemy stopniowo ulepszać widżet. Aplikacje są stopniowo ulepszane, najpierw definiując strukturę za pomocą statycznego kodu HTML. Następnie stosowany jest CSS, aby interfejs użytkownika był bardziej atrakcyjny wizualnie. Następnie JavaScript jest używany do dodawania interaktywnych funkcji i potencjalnie niektórych ulepszeń wizualnych. Funkcjonalność czasu rzeczywistego można dodać dokładnie w ten sam sposób. W przypadku widżetu wykonamy następujące czynności:

    1. Wygeneruj HTML widżetu na serwerze za pomocą PHP.

    2. Wystylizuj go za pomocą CSS.

    3. Dostosuj interfejs użytkownika za pomocą JavaScript i jQuery.

    4. Użyj funkcji Pusher, aby aktualizować widżet w czasie rzeczywistym, gdy pojawią się nowe darowizny.

    Tworzenie kodu HTML termometru

    Ze względu na dostępność dobrze jest mieć pewne wartości tekstowe, więc w przypadku HTML skupimy się na tym, dodając kilka elementów do wyświetlania wizualnego.

    < div class="thermometer-widget" >

    < div class="title" >Donation Thermometer< /div >

    < div class="cause" >A Good Cause< /div >

    < div class="figures" >

    < div class="goal" >

    < span class="label" >Our Goal< /span >

    £5,000< /span >

    < /div >

    < div class="current_total" >

    < span class="label" >Raised so far< /span >

    < span class="value" >£3,000< /span >

    < /div >

    < /div >

    < /div >

    Nie jest to zbyt ekscytujące, ale wyświetla proste wartości.

    Dodawanie widźetu do termometru za pomocą CSS

    Następnym etapem stopniowego ulepszania jest użycie CSS, więc zamierzamy dodać kilka dodatkowych elementów do HTML (w idealnym świecie nie dodawałbyś znaczników do stylizacji, ale jeszcze nie jesteśmy w tym świecie) . Nie wpływa to na wygląd widżetu, jeśli CSS nie jest dostępny; pozwala nam tylko dodać naszą wizualizację termometru do wyświetlacza.

    < div class="thermometer-widget" >

    < div class="title" >Donation Thermometer< /div >

    < div class="cause" >A Good Cause< /div >

    < div class="figures" >

    < div class="goal" >

    < span class="label" >Our Goal< /span >

    < span class="value" >£5,000< /span >

    < /div >

    < div class="current_total" >

    < span class="label" >Raised so far< /span >

    £3,000< /span >

    < /div >

    < /div >

    < div class="display" >

    < div class="thermometer" >

    < div class="top" >< /div >

    < div class="middle" >

    < div class="value" >< /div >

    < /div >

    < div class="base current_total" >

    < div class="value" >£3,000< /div >

    < /div >

    < /div >

    < /div >

    < /div >

    Warto podać trochę informacji dotyczących struktury HTML:

    .figures: Widzieliśmy to wcześniej. Zawiera kluczowe wartości widżetu.

    .display: To jest wizualny wyświetlacz termometru.

    .display .thermometer: Ma następujące wartości:

    .base: Jest to okrągła żarówka na dole termometru.

    .base .value: można użyć do pokazania wartości tekstowej funduszy, które zostały zebrane.

    . middle: jest to zewnętrzna część szyjki termometru.

    .middle .value: Wypełni się w zależności od podniesionej kwoty. Na razie nie ma wysokości.

    .top: To jest po prostu zakrzywiona górna część termometru.

    Jest do tego sporo CSS, więc nie zamierzam go tutaj umieszczać. Możesz wyświetlić surowy plik CSS na GitHub.

    Być może zauważyłeś podczas naszej dyskusji na temat struktury HTML, że żadna wysokość nie została zastosowana do .middle .value wizualizacji termometru, więc nie przekazuje ona poprawnie podniesionej kwoty. Ponieważ nie możemy jeszcze używać JavaScript, musimy wygenerować wartość wysokości na serwerze i zastosować wysokość do elementu. Aby to zrobić, musimy obliczyć procent celu, który został podniesiony, a następnie wyliczyć wysokość w pikselach, aby zastosować ją do wartości. Oto przykład robienia tego w PHP:

    < ?php

    $goal = 5000;

    $current_total = 3000;

    $current_percent_fraction = $current_total/$goal; // 0.6 = 60% full

    $middle_height = 165;

    $middle_height_value = $middle_height * $current_percent_fraction;

    ? >

    Kiedy generujemy kod HTML, możemy umieścić w elemencie styl wbudowany. Kod HTML będzie wyglądał następująco:

    < div class="middle" >

    < div class="value" style="height: 99px" >< /div >

    < /div >

    Poprawianie interfejsu użytkownika za pomocą JavaScript

    JavaScript został udostępniony w przeglądarkach internetowych, dzięki czemu możemy wzbogacić stronę internetową lub aplikację. W tym przypadku chcemy zastosować pewne efekty wizualne, które w innym przypadku spowodowałyby bardzo dużą ilość znaczników HTML. Zrobimy to za pomocą jQuery, więc pamiętaj o dołączeniu biblioteki jQuery:

    < script src = http://code.jquery.com/jquery-1.7.2.min.js > < /script>

    Znaczniki pomiarowe

    Możemy użyć JavaScript do ulepszenia widżetu na kilka sposobów. Najpierw możemy poprawić ogólny wygląd termometru, dodając znaczniki pomiarowe do środkowej części termometru. Gdybyśmy to zrobili za pomocą HTML, znaczniki stałyby się bardzo brzydkie, bardzo szybko. W poniższym kodzie odwołujemy się do .middle i .middle .value, a d pobiera ich wysokość. Uzyskujemy dostęp do kwoty celu charytatywnego i bieżącej sumy z interfejsu użytkownika i analizujemy wartości za pomocą funkcji getNumericVal. Korzystając z tych wszystkich informacji, wiemy, ile znaków dodamy do elementu .middle. Następnie dodajemy elementy, które reprezentują znaki. Jest tu trochę konfiguracji, aby uzyskać dostęp do elementów i wartości oraz obliczyć, ile znaków musimy narysować. Z tego powodu i ponieważ chcemy użyć tego ponownie w poniższych przykładach, zawarłem to w funkcji setUp. Teraz możemy skoncentrować się tylko na ulepszeniu interfejsu użytkownika.

    function getNumericVal(el, selector) {

    var el = el.find(selector);

    var val = el.text();

    val = parseInt(val.replace(/\D/g, ''), 10);

    return val;

    }

    function setUp(thermometerSelector) {

    var config = {};

    config.el = $(thermometerSelector);

    config.middleEl = config.el.find('.display .middle');

    config.middleValueEl = config.middleEl.find('.value');

    config.currentTotalEl = config.el.find('.current_total .value');

    config.numberOfMarks = parseInt(config.middleEl.height()/10, 10);

    config.goalValue = getNumericVal(config.el,

    '.figures .goal .value');

    config.currentTotalValue = getNumericVal(config.el, '.figures .current_total .value');

    config.pixelsPerValue = config.middleValueEl.height()/config.current TotalValue;

    config.valuePerMark = config.goalValue/config.numberOfMarks;

    return config;

    }

    Teraz, gdy mamy już wszystkie odniesienia do elementów i ustawione wartości, możemy dodać znaczniki do termometru:

    function addThermometerMarks(middleEl, numberOfMarks, valuePerMark) {

    for(var i = 1; i <= numberOfMarks; ++i) {

    var amount = parseInt(valuePerMark * i);

    var markEl = $('
    ');

    markEl.css({'position': 'absolute', 'bottom': (i*10) + "px"});

    markEl.attr('title', '?' + amount);

    var tooltip = $('
    £' + amount + '
    ');

    markEl.append(tooltip);

    middleEl.append(markEl);

    }

    }

    $(function() {

    var config = setUp('.thermometer-widget');

    addThermometerMarks(config.middleEl, config.numberOfMarks, config.valuePerMark);

    });

    Wartości znaczników, wyróżnienia najechania kursorem i wskazówki narzędzi.

    Oznaczenia termometru nie są zbyt przydatne, jeśli nie wiemy, jakie wartości reprezentują, więc wyświetlmy wartości, gdy użytkownik najedzie kursorem na elementy znacznika, wyświetlając podpowiedź. Dodamy również mały efekt podświetlenia. Na podstawie poprzedniego fragmentu kodu zobaczysz, że mamy już elementy podpowiedzi, więc teraz musimy je tylko pokazać po najechaniu kursorem. Robimy to poprzez dodanie klasy do znacznika, który zmienia display: none style to display: block, gdy użytkownik najedzie myszką na element.

    function addMarkHighlights(middleEl) {

    middleEl.find('.mark').hover(function() {

    var el = $(this);

    el.addClass('mark-selected');

    },

    function() {

    var el = $(this);

    el.removeClass('mark-selected');

    });

    }

    $(function() {

    var config = setUp('.thermometer-widget');

    addThermometerMarks(config.middleEl, config.numberOfMarks,

    config.valuePerMark);

    addMarkHighlights(config.middleEl);

    });

    Animowanie zmian wartości

    Końcowym efektem, który możemy dodać, jest animacja wysokości .środkowej .value termometru i wartości tekstowych poprzez zwiększenie ich od 0 do bieżącej sumy. Usuńmy z drogi funkcję użytkową. Poniższy tekst dodaje przecinki do wartości:

    function addCommas(number) {

    var number = number+''; var l = number.length; var out = '';

    var n = 0;

    for (var i=(l-1);i>=0;i--) {

    out = ''+number.charAt(i)+''+out;

    if ((l-i)%3 == 0 && i != 0) {

    out = ','+out;

    }

    n++;

    }

    return out;

    }

    Następnie animujmy tekst. To naprawdę jest tylko wizualna rzecz. Nie musi to być zbyt sprytne, więc będziemy po prostu zwiększać wartość co 50 ms o obliczoną wartość. Zamierzamy również zwrócić identyfikator setInterval, aby w razie potrzeby można go było wyczyścić w innym miejscu.

    function animateText(el, fromValue, toValue) {

    var total = fromValue;

    var interval = setInterval(function() {

    if(total < toValue) {

    // 2000ms for the animation, we update every 50ms

    total += parseInt((toValue-fromValue) / (2000/50));

    total = Math.min(total, toValue);

    el.html('£' + addCommas(total));

    }

    else {

    clearInterval(interval);

    }

    }, 50);

    return interval;

    }

    Teraz animujmy wizualizację termometru. Jest to bardzo łatwe dzięki funkcji jQuery.animate:

    function animateThermometer(valueEl, fromHeight, toHeight, totalEl, totalValue, callback) {

    // animate down really quickly. If a users sees it then it

    // won't look too bad.

    valueEl.animate({'height': fromHeight + 'px'}, 'fast',

    function() {

    // animate back up slowly. Cool!

    valueEl.animate({'height': toHeight}, '2000', function() {

    totalEl.html('£' + addCommas(totalValue));

    callback();

    });

    });

    }

    Na koniec połączmy wszystkie te funkcje i w pełni stylizujmy i animujmy termometr. Przeniesiemy również niektóre wywołania konfiguracji komentarzy do funkcji addBehaviours.

    function animateValues(valueEl, totalEl, fromValue, toValue, goalValue, pixelsPerValue) {

    var fromHeight = pixelsPerValue*fromValue;

    var toHeight = Math.min(pixelsPerValue*toValue,

    pixelsPerValue*goalValue);

    var interval = animateText(totalEl, fromValue, toValue);

    animateThermometer(valueEl, fromHeight, toHeight, totalEl, toValue, function() {

    clearInterval(interval);

    });

    return interval;

    };

    function addBehaviours(config, setInitialValues) {

    setInitialValues = (setInitialValues === undefined?

    true: setInitialValues);

    addThermometerMarks(config.middleEl, config.numberOfMarks,

    config.valuePerMark);

    addMarkHighlights(config.middleEl);

    if(setInitialValues) {

    animateValues(config.middleValueEl, config.currentTotalEl, 0,

    config.currentTotalValue, config.goalValue,

    config.pixelsPerValue);

    }

    }

    $(function() {

    var config = setUp('.thermometer-widget');

    addBehaviours(config);

    });

    Jeśli teraz wyświetlisz widżet, zobaczysz animację wartości i paska termometru, co jest miłym małym efektem.

    Dodawanie aktualizacji w czasie rzeczywistym

    Wydaje się, że musieliśmy ciężko pracować, aby dojść tak daleko. Ale niesamowitą wiadomością jest to, że dodawanie aktualizacji w czasie rzeczywistym do widżetu termometru charytatywnego jest bardzo łatwe. Najpierw dodajmy bibliotekę Pusher JavaScript do strony:

    < script src = http://js.pusher.com/1.12/pusher.min.js > < /script >

    Łączymy się z usługą Pusher, tworząc nową instancję Pusher i przekazując klucz aplikacji (aby go uzyskać, musisz założyć bezpłatne konto Pusher). Zasubskrybujemy również publiczny kanał darowizn, z którym będzie powiązane wydarzenie new_donation. To zdarzenie zostanie uruchomione za każdym razem, gdy zostanie przekazana nowa darowizna na cel. Samo wydarzenie powie nam, kto przekazał darowiznę, ile przekazał i jaka jest nowa suma. JSON do tego będzie wyglądał następująco:

    {

    "who": "Phil Leggetter",

    "howMuch": "20",

    "newTotal": "3020"

    }

    Teraz, gdy już to wiemy, możemy również utworzyć funkcję o nazwie animateDonation, która wywołuje naszą funkcję animateValues ze zaktualizowanymi wartościami, aby wyświetlić naszą aktualizację w czasie rzeczywistym. Kod do tego wszystkiego jest następujący:

    function animateDonation( middleValueEl, currentTotalEl, currentTotal, newTotal, pixelsPerValue, goalValue ) {

    var newHeightPixels = parseInt(pixelsPerValue * newTotal, 10);

    return animateValues(middleValueEl, currentTotalEl,

    currentTotal, newTotal, goalValue, pixelsPerValue);

    };

    $(function() {

    var config = setUp('.thermometer-widget');

    addBehaviours(config, false);

    var pusher = new Pusher("006c79b1fe1700c6c10d");

    var channel = pusher.subscribe('donations-channel');

    var animateInterval = null;

    channel.bind('new_donation', function(data) {

    if(animateInterval) {

    clearInterval(animateInterval);

    }

    var newTotal = data.newTotal;

    var currentTotalValue = getNumericVal(config.el,

    '.figures .current_total .value');

    animateInterval = animateDonation(config.middleValueEl,

    config.currentTotalEl, currentTotalValue, newTotal,

    config.pixelsPerValue, config.goalValue);

    });

    });

    Funkcja animateDonation zwraca interwał animacji, co daje nam możliwość anulowania trwającej animacji, jeśli pojawi się nowa aktualizacja. To zatrzymuje dwie animacje działające w tym samym czasie, w których możemy zobaczyć naprawdę szalone rzeczy. Jesteśmy teraz gotowi do utworzenia kodu, który wyzwala aktualizację. W tym celu zamierzamy użyć PHP i biblioteki Pusher PHP firmy Squeeks, ale to samo można osiągnąć w innych językach przy użyciu jednej z bibliotek Pusher Server. Zamierzamy stworzyć usługę internetową, która umożliwia przekazywanie darowizn. Będzie składał się tylko z parametrów kto i jak_dużo. Będziemy przechowywać te dane w bazie danych MySQL (ale nie będziemy tutaj omawiać szczegółów konfiguracji) i zaktualizować sumę bieżącą. Oto kod, zanim dodamy kod Pusher:

    < ?php

    require('config.php');

    $con = mysql_connect("localhost", $db_username, $db_password);

    if (!$con)

    {

    die('Could not connect: ' . mysql_error());

    }

    mysql_select_db($db_name, $con);

    $who = mysql_real_escape_string($_GET['who']);

    $how_much = mysql_real_escape_string($_GET['how_much']);

    if( !$who || !how_much || !is_numeric($how_much) ) {

    die('unsupported who and how_much values');

    }

    $running_total = 0;

    $last_update = "SELECT *

    FROM $db_tablename ORDER BY id DESC LIMIT 1";

    $result = mysql_query($last_update);

    if($result) {

    $row = mysql_fetch_array($result);

    $running_total = $row['running_total'];

    }

    $running_total = $running_total + $how_much;

    $insert_query = "INSERT INTO $db_tablename (who, how_much, running_total) ";

    $insert_query .= sprintf( "VALUES('%s', %f, %f)", $who, $how_much, $running_total );

    $insert_result = mysql_query($insert_query);

    if(!$insert_result) {

    die('insert query failed' . mysql_error());

    }

    mysql_close($con);

    ? >

    Teraz dodajmy magię czasu rzeczywistego. Wszystko, co musimy zrobić, to dołączyć bibliotekę Pusher PHP, utworzyć instancję Pusher, umieścić dane, które chcemy wysłać do tablicy i wywołać zdarzenie, wywołując $ pusher-> trigger(). Zmienne przekazywane do konstruktora Pusher są zdefiniowane w config.php.

    require('Pusher.php');

    $pusher = new Pusher($pusher_key, $pusher_secret, $pusher_app_id);

    $channel_name = 'donations-channel';

    $values = array('who' ⇒ $who, 'howMuch' ⇒ $how_much, 'newTotal' ⇒

    $running_total);

    $pusher→trigger($channel_name, 'new_donation', $values);

    Otóż to! Naprawdę tak łatwo jest wywołać zdarzenie w czasie rzeczywistym. A ponieważ wykonaliśmy całą ciężką pracę na kliencie, termometr organizacji charytatywnej w czasie rzeczywistym aktualizuje się w czasie rzeczywistym. Ponieważ PHP pobiera wartości z żądania GET, możemy przetestować tę funkcję, przechodząc do naszego pliku PHP, który nazwiemy donate.php, przekazując wymagane parametry ciągu zapytania GET:

    donate.php? who = Phil & how_much = 100

    Oto przykład formularza, który można przesłać do właśnie utworzonego pliku PHP. Dostarczono również trochę JavaScript, co oznacza, że formularz jest przesyłany za pomocą Ajax.

    < form id="donate_form" action="donate.php" >

    < label for="who" >Name< /label >< input type="text" value="Anon" name="who" / >


    < label for="how_much" >How Much< /label >< input type="number" value="100.00" name="how_much" / >< br />

    < label for="reset_total" >Reset?< /label >< input name="reset_total" type="checkbox" value="1" / >

    < input type="submit" value="Donate!" / >

    < /form >

    < script >

    $(function() {

    $( '#donate_form' ).submit(function() {

    var form = $(this);

    var values = form.serialize();

    $.ajax({

    url: 'donate.php',

    data: values

    });

    return false;

    });

    });

    < /script >

    Podsumowanie

    Więc co osiągnęliśmy?

    * Stopniowo ulepszany widget, który zaczyna swoje życie jako statyczny, nudny HTML

    * Dodano CSS i kilka dodatkowych elementów HTML, w które można zamienić widżet w coś bardziej atrakcyjnego wizualnie

    * Użyłem JavaScript do dalszej aktualizacji interfejsu użytkownika, dodając znaczniki, które zmieniłyby HTML w bałagan, i dodając animacje

    * Używany Pusher do dodawania aktualizacji w czasie rzeczywistym do widżetu, dzięki czemu za każdym razem, gdy pojawia się nowa darowizna, aktualizuje się wartości

    Naprawdę interesujące jest to, że dodawanie komponentów czasu rzeczywistego do widżetu zajęło ułamek czasu, który wymagał wszystkiego innego. Tak więc, jeśli masz już aplikację dynamiczną, naprawdę łatwo jest dodać odrobinę magii czasu rzeczywistego, aby jeszcze bardziej ją ulepszyć i uczynić ją bardziej wciągającą.

    Hack No.74

    Twórz wtyczki dla jWebSocket

    jWebSocket to wieloplatformowa platforma komunikacji w czasie rzeczywistym składająca się z serwera i klientów stacjonarnych i mobilnych, internetowych i natywnych. Dzięki niemu możesz tworzyć oparte na HTML5 aplikacje do przesyłania strumieniowego i komunikacji w Internecie. jWebSocket ma własne implementacje serwerowe, ale także bezproblemowo integruje istniejące serwery, takie jak Tomcat, Glassfish i Jetty. Oprócz klienta JavaScript dla aplikacji internetowych czasu rzeczywistego opartych na przeglądarce, dostępne są również klienty dla urządzeń mobilnych z systemem Android, iOS, Windows Phone, BlackBerry i Java ME. W przypadku urządzeń stacjonarnych jWebSocket obsługuje Java SE, C # i Python. jWebSocket jest darmowy i open source. Jedną z jego głównych zalet jest to, że jest wyposażony w potężny rdzeń, który można łatwo rozszerzyć za pomocą wtyczek i aplikacji gniazd internetowych do setek tysięcy równoczesnych połączeń. Szyfrowanie SSL i system filtrów wiadomości zapewniają wysoki poziom bezpieczeństwa. Dzięki standaryzacji IETF i W3C, gniazda sieciowe zapewniają wysoki poziom ochrony inwestycji oraz łatwość utrzymania kodu na wszystkich nowoczesnych platformach. Korzystanie przez nich ze stałych, pełnodupleksowych połączeń TCP zamiast półdupleksowych połączeń HTTP zapewnia znaczny wzrost szybkości Twojej aplikacji, a także zwiększona odpowiedzialność i większe zadowolenie użytkowników. jWebSocket jest przeznaczony do tworzenia innowacyjnych aplikacji do przesyłania strumieniowego i komunikacji w Internecie opartych na HTML5. Gniazda sieciowe HTML5 zastąpią istniejące podejście XHR, a także usługi Comet, nową, elastyczną i ultraszybką dwukierunkową technologią komunikacyjną TCP. Funkcje rezerwowe zapewniające kompatybilność wsteczną zapewniają, że aplikacja nadal działa w sposób przezroczysty nawet w starszych środowiskach. jWebSocket jest przeznaczony do zastosowań, takich jak gry online, współpraca online oraz usługi przesyłania strumieniowego i przesyłania wiadomości w czasie rzeczywistym. Stanowi doskonałą podstawę dla złożonych klastrów obliczeniowych, architektur zorientowanych na usługi i wszelkiego rodzaju interfejsów między nową a już istniejącą technologią. Dzięki dużej liczbie wtyczek dostarczanych z pakietami instalacyjnymi, jWebSocket jest odpowiedni do zaspokojenia wszystkiego, od prostych potrzeb komunikacyjnych po złożone, heterogeniczne rozwiązania do przesyłania wiadomości w czasie rzeczywistym i synchronizacji danych. Wtyczki są gotowe do użycia, ale można je również rozszerzyć w celu dostosowania do indywidualnych wymagań. W tym hacku pokażę, jak skonfigurować serwer, użyć bibliotek klienta do nawiązania komunikacji i rozszerzyć jWebSocket zarówno o wtyczki po stronie klienta, jak i po stronie serwera.

    Uruchamianie serwera jWebSocket

    Serwer jWebSocket jest w całości napisany w Javie, więc działa na prawie wszystkich systemach operacyjnych, w tym Windows, Mac OS X i Linux. Jest to oprogramowanie typu open source i bezpłatne do pobrania. Aby to uzyskać, po prostu pobierz pakiet serwera jWebSocket, jWebSocketServer- .zip, z obszaru pobierania witryny jWebSocket. Zawiera plik jWebSocketServer- .jar, wszystkie wymagane biblioteki oraz skrypty jWebSocketServer- .bat i .sh do uruchomienia serwera. Rozpakuj archiwum do wybranego folderu (np. / Etc dla systemów Unix / Linux, / Applications dla środowisk Mac OS X lub c: \ program files \ dla środowisk Windows). Archiwum zawiera folder jWebSocket- , który jest folderem głównym serwera jWebSocket Server. Plik jWebSocketServer- .jar w folderze / bin zawiera wszystkie wymagane biblioteki i zapewnia gotową do użycia strukturę folderów. Można go łatwo uruchomić z powłoki lub okna wiersza poleceń bez żadnej instalacji lub specjalnej konfiguracji.

    •  W systemie Windows: jWebSocketServer.bat

    •  W systemie Linux: jWebSocketServer.sh

    •  W przypadku systemu Mac OS X: jWebSocketServer.command

    Podobnie jak w przypadku zwykłych aplikacji komputerowych, serwer jest wyłączany po wylogowaniu z systemu. Dlatego w przypadku systemów produkcyjnych zaleca się korzystanie z usługi jWebSocket (dla systemu Windows) lub aplikacji internetowej jWebSocket (dla wszystkich systemów operacyjnych). W pakiecie instalacyjnym znajdują się odpowiednie skrypty do zainstalowania i odinstalowania usługi.

    Wymagania wstępne na serwerze

    Ponieważ serwer jWebSocket jest oparty na czystej technologii Java, upewnij się, że na serwerze jest zainstalowane środowisko Java Runtime Environment (JRE) 1.6 lub nowsze oraz że zmienna środowiskowa JAVA_HOME odnosi się do folderu głównego tej instalacji Java. Do zmiennej środowiskowej PATH należy dodać ścieżkę do pliku wykonywalnego Java. W przeciwnym razie może być konieczne dostosowanie dostarczonej partii początkowej lub skryptu.

    jWebSocket "Hello World" dla przeglądarek

    Utworzenie od podstaw pierwszego klienta jWebSocket "Hello World" jest proste. Nawet jeśli Twój serwer jWebSocket jeszcze nie działa, do pierwszych testów możesz użyć serwera live jWebSocket pod adresem ws: //jwebsocket.org: 8787.

    Zasadniczo klient inicjuje komunikację przez gniazdo sieciowe między nim a serwerem. Po ustanowieniu połączenia klient wysyła wiadomości do serwera lub do innych klientów za pośrednictwem serwera. W przeciwnym kierunku serwer wysyła wiadomości do klienta przy użyciu tego samego połączenia. O ile połączenie nie zostanie przerwane ani przez serwer, ani przez klienta, obaj partnerzy mogą wymieniać dowolne komunikaty dwukierunkowo. To prawie wszystko, czego potrzebujesz, aby rozpocząć swój pierwszy projekt jWebSocket.

    Osadzanie skryptu jWebSocket

    Jedyną rzeczą, którą musisz zrobić, aby używać jWebSocket na swoich stronach internetowych i otworzyć swoją witrynę na świat dwukierunkowych aplikacji czasu rzeczywistego, jest umieszczenie pojedynczego tagu skryptu w sekcji head kodu HTML:

    < script type = "text / javascript" src = "< path_to_jWebSocket.js > /jwebsocket.js" >

    < /script >

    Dzięki temu jWebSocket będzie dostępny dla Twojej strony. Możesz użyć pełnego, udokumentowanego kodu źródłowego w wierszu jWebSocket.js, który jest najlepszym wyborem do nauki jWebSocket, lub wersji zminimalizowanej w jWebSocket_min.js zalecanej dla twojego systemu produkcyjnego.

    Tworzenie instancji jWebSocketClient

    jWebSocket udostępnia klasę jWebSocketJSONClient w ramach przestrzeni nazw specyficznej dla jWebSocket jws. Ta klasa udostępnia metody łączenia i rozłączania, a także wymiany wiadomości z serwerem przy użyciu protokołu JSON. Przestrzeń nazw pozwala uniknąć konfliktów nazewnictwa z innymi strukturami.

    // jws.browserSupportsWebSockets sprawdza, czy gniazda sieciowe są dostępne

    // natywnie, przez FlashBridge lub przez ChromeFrame.

    if (jws.browserSupportsWebSockets ()) {

    jWebSocketClient = new jws.jWebSocketJSONClient ();

    // Opcjonalnie włącz sterowanie GUI tutaj

    } else {

    // Opcjonalnie wyłącz kontrolki GUI tutaj

    var lMsg = jws.MSG_WS_NOT_SUPPORTED;

    alert (lMsg);

    }

    Łączenie i logowanie

    Aby zainicjować połączenie od klienta do serwera, możesz skorzystać z metody logowania jWebSocketClient. Ta metoda łączy się z serwerem i przekazuje nazwę użytkownika i hasło do uwierzytelnienia za jednym razem.

    log( "Connecting to " + lURL + " and logging in as '" + gUsername + "'..." );

    var lRes = jWebSocketClient.logon( lURL, gUsername, lPassword, {

    // OnOpen callback

    OnOpen: function( aEvent ) {

    log( "< font style='color:#888'>jWebSocket connection established.

    < /font >" );

    },

    // OnMessage callback

    OnMessage: function( aEvent, aToken ) {

    log( "jWebSocket '" + aToken.type

    + "' token received, full message: '" + aEvent.data + "'
    " );

    },

    // OnClose callback

    OnClose: function( aEvent ) {

    log( "< font style='color:#888' >jWebSocket connection closed.

    < /font >" );

    }

    });

    Serwer przypisuje klientowi unikalny identyfikator, dzięki czemu do konkretnego klienta można zawsze adresować unikalne adresy, nawet jeśli ten sam użytkownik loguje się w wielu instancjach przeglądarki.

    Wysyłanie i nadawanie tokenów

    Jeśli połączenie zostało pomyślnie nawiązane, klient wysyła swoje komunikaty metodą wysyłania do innego klienta lub rozgłasza je do wszystkich połączonych klientów za pomocą metody rozgłaszania jWebSocketClient.

    // lMsg is a string

    if( lMsg.length > 0 ) {

    var lRes = jWebSocketClient.broadcastText(

    "", // broadcast to all clients (not limited to a certain pool)

    lMsg // broadcast this message

    );

    if( lRes.code != 0 ) {

    // display error

    }

    }

    Wysyłanie wiadomości jest zawsze nieblokujące - to znaczy zarówno wysyłanie, jak i rozgłaszanie nie czekają, aż zostanie zwrócony potencjalny wynik. Opcjonalny wynik jest zwracany asynchronicznie, zgodnie z opisem poniżej.

    Przetwarzanie wiadomości przychodzących

    Wiadomości z serwera do klienta są przekazywane asynchronicznie. Dlatego klasa jWebSocketClient udostępnia zdarzenie OnMessage. Jak już pokazano w metodzie logowania, aplikacja po prostu dodaje odbiornik do tego zdarzenia i przetwarza wiadomość zgodnie z potrzebami.

    // OnMessage callback

    OnMessage: function( aEvent, aToken ) {

    log( "< font style='color:#888' >jWebSocket '" + aToken.type + "' token received, full message: '" + aEvent.data + "'< /font >"

    ); } Pełne odniesienie do zestawu tokenów jWebSocket znajdziesz w przewodniku dla programistów online.

    Wylogowywanie i rozłączanie

    Na żądanie zarówno serwer, jak i klient mogą zakończyć istniejące połączenie. Po stronie klienta odbywa się to za pomocą metody close jWebSocketClient

    if (jWebSocketClient) {

    jWebSocketClient.close ();

    }

    Serwer automatycznie przerywa połączenie po pewnym okresie braku aktywności na linii. W takim przypadku wyzwalane jest zdarzenie OnClose, które może być obsługiwane przez odpowiednie wywołanie zwrotne, jak pokazano wcześniej w metodzie logowania. Limit czasu można skonfigurować i opcjonalnie można uruchomić funkcję utrzymywania aktywności lub ponownego połączenia

    Rozszerzenie jWebSocket za pomocą wtyczek

    Jedną z najpotężniejszych funkcji jWebSocket jest możliwość rozbudowy za pomocą wtyczek. Wtyczki rozszerzają funkcjonalność serwera jWebSocket Server, udostępniając metody przetwarzania wiadomości przychodzących od klientów, a także innych zdarzeń, takich jak połączenie klienta lub odłączenie klienta. Wiadomości przychodzące są filtrowane przez łańcuch filtrów jWebSocket, dzięki czemu zapewniają wysoki poziom bezpieczeństwa. Wtyczki można ładować programowo przez kod - szczególnie w celach programistycznych - lub dynamicznie w czasie wykonywania, po prostu odwołując się do niego w pliku konfiguracyjnym jWebSocket.xml, co jest zalecane w środowiskach produkcyjnych lub jeśli chcesz rozpowszechniać wtyczki. W przeciwieństwie do aplikacji WebSocket, wtyczki mają implementować usługi ogólne, a nie logikę specyficzną dla aplikacji. Główną zaletą wtyczek jest to, że są one traktowane jako oddzielne, samowystarczalne fragmenty oprogramowania, które można nawet dystrybuować oddzielnie - o otwartym lub zamkniętym kodzie źródłowym - albo udostępniać w wielu aplikacjach. Utworzenie pierwszej wtyczki i udostępnienie jej funkcji w witrynie jest prostym procesem:

    1. Utwórz wtyczkę po stronie serwera.

    2. Dodaj swoją wtyczkę do serwera jWebSocket.

    3. Utwórz wtyczkę po stronie klienta (zalecane w celu utrzymania czystości modułów i przestrzeni nazw).

    4. Korzystaj z funkcji wtyczek na swoich stronach internetowych.

    Utwórz wtyczkę po stronie serwera

    Pierwszym krokiem w celu rozszerzenia funkcjonalności serwera jWebSocket jest utworzenie wtyczki po stronie serwera. Wtyczka jest zwykle implementowana jako potomek klasy TokenPlugIn, która jest zawarta w jWebSocket. Aby opracować własne wtyczki, zaleca się tworzenie ich w osobnych pakietach. Ułatwi to późniejsze rozpowszechnianie ich jako pojedynczych plików .jar, które można dodać do każdej instancji serwera jWebSocket Server. Poniższa lista przedstawia prostą wtyczkę z pojedynczym "poleceniem" requestServerTime:

    public class SamplePlugIn extends TokenPlugIn {

    private static Logger log = Logging.getLogger(SamplePlugIn.class);

    // if namespace changed update client plug-in accordingly!

    private static String NS_SAMPLE = JWebSocketConstants.NS_BASE + ".plugins.sample";

    private static String SAMPLE_VAR = NS_SAMPLE + ".started";

    public SamplePlugIn() {

    if (log.isDebugEnabled()) {

    log.debug("Instantiating sample plug-in...");

    }

    // specify default name space for sample plugin

    this.setNamespace(NS_SAMPLE);

    }

    @Override

    public void connectorStarted(WebSocketConnector aConnector) {

    // this method is called every time when a client

    // connected to the server

    aConnector.setVar(SAMPLE_VAR, new Date().toString());

    }

    @Override

    public void connectorStopped(WebSocketConnector aConnector, CloseReason aCloseReason) {

    // this method is called every time when a client

    // disconnected from the server

    }

    @Override

    public void engineStarted(WebSocketEngine aEngine) {

    // this method is called when the engine has started

    super.engineStarted(aEngine);

    }

    @Override

    public void engineStopped(WebSocketEngine aEngine) {

    // this method is called when the engine has stopped

    super.engineStopped(aEngine);

    }

    @Override

    public void processToken(PlugInResponse aResponse,

    WebSocketConnector aConnector, Token aToken) {

    // get the type of the token

    // the type can be associated with a "command"

    String lType = aToken.getType();

    // get the namespace of the token

    // each plug-in should have its own unique namespace

    String lNS = aToken.getNS();

    // check if token has a type and a matching namespace

    if (lType != null && lNS != null && lNS.equals(getNamespace())) {

    // get the server time

    if (lType.equals("requestServerTime")) {

    // create the response token

    // this includes the unique token-id

    Token lResponse = createResponse(aToken);

    // add the "time" and "started" field

    lResponse.put("time", new Date().toString());

    lResponse.put("started", aConnector.getVar(SAMPLE_VAR));

    // send the response token back to the client

    sendToken(aConnector, lResponse);

    }

    }

    }

    }

    Dodaj swoją wtyczkę do serwera jWebSocket

    Poniższa lista pokazuje, jak dodać nową wtyczkę do łańcucha wtyczek serwera jWebSocket Server:

    // uruchom podsystem serwera jWebSocket

    JWebSocketFactory.start (...);

    // dodaj swoją wtyczkę do łańcucha wtyczek serwera jWebSocket Server

    TokenServer lTS = (TokenServer) JWebSocketFactory.getServer ("ts0");

    SamplePlugIn lSP = nowa SamplePlugIn ();

    lTS.getPlugInChain (). addPlugIn (lSP);

    Klasa JWebSocketFactory ładuje i uruchamia serwer jWebSocket Server, w tym wszystkie wymagane biblioteki, domyślne wtyczki i filtry. Najpierw TokenServer jest uzyskiwany na podstawie jego identyfikatora, który jest konfigurowany w pliku konfiguracyjnym jWebSocket.xml. Następnie tworzona jest instancja nowej wtyczki i dodawana do łańcucha wtyczek TokenServer. Otóż to; wszystkie funkcje nowej wtyczki są teraz dostępne dla klientów.

    Tworzenie wtyczki po stronie klienta

    Ogólnie rzecz biorąc, istnieją dwa sposoby uzyskania dostępu do wtyczki po stronie serwera. Po pierwsze, możesz po prostu użyć metody sendToken biblioteki JavaScript jWebSocket.js i zaimplementować detektor do swojej metody OnMessage. Zachęcałbym jednak do dostarczenia osobnego pliku JavaScript jako wtyczki po stronie klienta, aby zachować moduł i interfejs API w czystości i ułatwić późniejszą dystrybucję pakietu w dwóch plikach (wtyczka po stronie serwera i wtyczka po stronie klienta -w). Poniższa lista przedstawia, w jaki sposób można utworzyć wtyczkę klienta jWebSocket. Dostarcza metodę requestServerTime do JavaScript jWebSocketTokenClient - a tym samym do malejącej , jWebSocketJSONClient również przez dziedziczenie.

    jws.SamplesPlugIn = {

    // namespace for shared objects plugin

    // if namespace is changed update server plug-in accordingly!

    NS: jws.NS_BASE + ".plugins.samples",

    processToken: function( aToken ) {

    // check if namespace matches

    if( aToken.ns == jws.SamplesPlugIn.NS ) {

    // here you can handle incoming tokens from the server

    // directy in the plug-in if desired.

    if( aToken.reqType == "requestServerTime" ) {

    // this is just for demo purposes

    // don't use blocking call here!

    alert( "jWebSocket Server returned: " + aToken.time );

    }

    }

    },

    requestServerTime: function( aOptions ) {

    var lRes = this.createDefaultResult();

    if( this.isConnected() ) {

    var lToken = {

    ns: jws.SamplesPlugIn.NS,

    type: "requestServerTime"

    };

    this.sendToken( lToken, aOptions );

    } else {

    lRes.code = ?1;

    lRes.localeKey = "jws.jsc.res.notConnected";

    lRes.msg = "Not connected.";

    }

    return lRes;

    }

    }

    // add the jWebSocket Samples PlugIn into the TokenClient class

    jws.oop.addPlugIn( jws.jWebSocketTokenClient, jws.SamplesPlugIn );

    Użyj wtyczek na swoich stronach internetowych

    Ostatnim działaniem mającym na celu udostępnienie funkcji wtyczki Twojej aplikacji jest dodanie łącza do nowej wtyczki klienta na stronach internetowych:

    < script type="text/javascript" src="< url >/res/js/jWebSocket.js" >

    < /script >

    < script type="text/javascript" src="< url >/res/js/jwsSamplesPlugIn.js" >

    < /script >

    Dołączone wtyczki jWebSocket

    jWebSocket jest już dostarczany z ogromnym i stale rosnącym zestawem gotowych do użycia wtyczek. Tabela zawiera szybki przegląd.

    Wtyczki dołączone do jWebSocket

    Wtyczka: Cel

    Wtyczka API: publikowanie interfejsów API WebSocket, takich jak WSDL dla usług internetowych

    Arduino-Plug-in: sprzętowe zdalne sterowanie i monitorowanie za pomocą Arduino

    Benchmark-Plug-in: obsługa pomiaru prędkości i profilowania komunikacji

    Channel-Plug-in: Implementacja modelu komunikacji opartego na kanale

    Wtyczka do czatu: obsługa pokojów rozmów, czatów grupowych i prywatnych w czasie rzeczywistym

    Wtyczka Events: Implementacja modelu komunikacji opartego na zdarzeniach

    Wtyczka systemu plików: foldery publiczne i prywatne z powiadomieniami o aktualizacjach w czasie rzeczywistym

    JCaptcha-Plug-in: obsługa Captcha

    Wtyczka JDBC: dostęp do bazy danych, pamięć podręczna, synchronizacja i powiadomienia o aktualizacjach

    Wtyczka JMX: interfejs WebSocket z rozszerzeniami zarządzania Java EE

    Wtyczka JMS: interfejs WebSocket z usługami Java EE Messaging Services

    jQuery-Plug-in: obsługa wymiany danych w czasie rzeczywistym dla jQuery i jQuery Mobile

    Logging-Plug-in: debugowanie, logowanie serwera i klienta za pomocą gniazd sieciowych

    Wtyczka do poczty: obsługa poczty dla SMTP, POP3 i IMAP przez gniazda sieciowe

    Wtyczka do monitorowania: zdalne monitorowanie serwera za pomocą biblioteki Sigar

    Wtyczka do raportowania: obsługa raportów Jasper przez gniazda sieciowe

    Dodatek RPC: zdalne wywołania procedur, klient-serwer, serwer-klient, klient-klient

    Sencha-Plug-in: obsługa wymiany danych w czasie rzeczywistym dla Sencha / Sencha Touch / ExtJS

    Shared-Canvas-Plugin: Demo do udostępniania płótna HTML5 w czasie rzeczywistym; wirtualna tablica

    Wtyczka Shared-Objects: obsługa synchronizacji danych między klientami w czasie rzeczywistym

    Wtyczka SMS: obsługa dystrybucji wiadomości SMS przez różnych dostawców

    Wtyczka Statistics: Dostęp i statystyki użytkownika dla usług opartych na technologii WebSocket

    Wtyczka do przesyłania strumieniowego: Demo do realizacji usług przesyłania strumieniowego za pośrednictwem gniazd internetowych

    Wtyczka testowa: wsparcie programistów w zakresie przetwarzania tokenu, odpowiedzi i błędów

    Wtyczka Twittera: interfejs WebSocket do usług przesyłania strumieniowego na Twitterze

    Wtyczka XMPP: interfejs WebSocket do usług komunikacyjnych Jabber / XMPP

    Jeśli preferujesz język programowania Java, powinieneś być teraz gotowy do integracji gniazd sieciowych z aplikacjami - a jeśli naprawdę się ekscytujesz, może nawet stworzysz własną wtyczkę do jWebSocket

    Hack No.75

    Powiadomienia push do przeglądarki ze zdarzeniami wysłanymi przez serwer

    Stworzony przez Operę, Server-Sent Events standaryzuje technologie Comet. Standard ma zapewnić natywne aktualizacje w czasie rzeczywistym za pośrednictwem prostego interfejsu API JavaScript o nazwie EventSource, który łączy się z serwerami, które asynchronicznie wysyłają aktualizacje danych do klientów za pośrednictwem przesyłania strumieniowego HTTP. Zdarzenia wysyłane przez serwer korzystają z pojedynczego, jednokierunkowego, trwałego połączenia między przeglądarką a serwerem. W przeciwieństwie do interfejsu API WebSocket, zdarzenia wysyłane przez serwer i obiekt EventSource używają protokołu HTTP do włączania funkcji wypychania serwera w czasie rzeczywistym w aplikacji. Strumieniowanie HTTP poprzedza WebSocket API i jest często określane jako Comet lub serwer push. Ekscytującą częścią jest to, że API Server-Sent Events ma na celu ujednolicenie techniki Comet, dzięki czemu jej implementacja w przeglądarce.

    Co to jest przesyłanie strumieniowe HTTP?

    W przypadku standardowego żądania HTTP i odpowiedzi między przeglądarką internetową a serwerem WWW, serwer zamknie połączenie po zakończeniu przetwarzania żądania. Strumieniowanie HTTP lub Comet różni się tym, że serwer utrzymuje stałe, otwarte połączenie z przeglądarką. Należy zauważyć, że nie wszystkie serwery internetowe umożliwiają przesyłanie strumieniowe. Tylko serwery zdarzeń, takie jak Node.js, Tornado i Thin, są wyposażone w obsługę pętli zdarzeń, która jest optymalna do obsługi przesyłania strumieniowego HTTP. Te nieblokujące serwery bardzo dobrze obsługują trwałe połączenia z dużej liczby równoczesnych żądań. Pełne omówienie serwerów zdarzonych i wielowątkowych wykracza poza zakres tej książki, ale biorąc to pod uwagę, w tym hacku przedstawię bardzo prosty przykład implementacji serwera eventowego, abyś mógł zacząć. Dostarczam prosty JavaScript oparty na przeglądarce do łączenia się z serwerem oraz implementację po stronie serwera przy użyciu Ruby, Thin i Sinatra. Dla przypomnienia, jest to również bardzo łatwe w przypadku Node.js. Miej oko na towarzyszące repozytoria Git, aby uzyskać aktualizację w przyszłości.

    Ruby′s Sinatra

    Dokumentacja Sinatra opisuje się jako "DSL do szybkiego tworzenia aplikacji internetowych w Rubim przy minimalnym wysiłku". Ten tekst koncentruje się głównie na Node.js (serwer HTTP) i Express.js (web framework aplikacji), aby szybko wygenerować implementacje po stronie serwera w celu zhakowania funkcjonalności. Byłoby źle nie wspomnieć o Rubim, Railsach i Sinatrze w tym samym lub podobnym świetle, w jakim mam Node.js w tym tekście. Chociaż nauka Ruby to kolejna krzywa uczenia się, w szerszym schemacie języków programowania jest to mniej zniechęcająca niż większość. I jak głosi większość zagorzałych rubistów, jest to prawdopodobnie najbardziej elegancki i zabawny ze wszystkich współczesnych języków programowania. Ruby on Rails i jego młodszy brat Sinatra są również świetnymi frameworkami dla aplikacji internetowych, od których możesz zacząć, jeśli jesteś nowy w tworzeniu aplikacji internetowych. Podobnie jak Node.js i Express.js, Sinatra sprawia, że tworzenie małych implementacji serwerów jest prawie trywialne. W kontekście hacków HTML5 pozwala nam to skupić się na programowaniu w przeglądarce. Na razie zbudujmy prosty serwer przesyłania strumieniowego HTTP przy użyciu Sinatry. Zacząłem od Ruby on Rails lub Sinatra, zapoznaj się ze świetną dokumentacją dostępną odpowiednio na rubyonrails.org i sinatrarb.com. Alternatywnie możesz pominąć poniższe czynności, tworząc klon Gita w repozytorium http: // github / html5hacks / Chapter9 i postępując zgodnie z instrukcjami.

    Tworzenie powiadomień push

    Naszym celem w tym hacku jest zbudowanie prostego serwera strumieniowego i korzystanie z EventSource, aby otworzyć trwałe połączenie z przeglądarki. Następnie będziemy przesyłać powiadomienia z jednej przeglądarki administratora do wszystkich podłączonych odbiorników. Brzmi prosto, prawda? Zacznijmy.

    Prosty serwer strumieniowy http

    Najpierw otworzymy plik i nadamy mu nazwę stream.rb. Następnie dodamy:

    require 'json'

    require 'sinatra'

    Następnie skonfigurujemy folder publiczny i ustawimy serwer tak, aby używał zdarzonego serwera Ruby, Thin:

    set :public_folder, Proc.new { File.join(root, "public") }

    set server: 'thin'

    Teraz musimy ustawić dwie trasy do obsługi naszych dwóch stron: index i admin. Użyjemy ERB jako naszego języka szablonów. Szczegóły ERB wykraczają poza zakres tej książki, ale nasze użycie jest bardzo minimalne. Więcej informacji na temat ERB można znaleźć na ruby-doc.org.

    get '/' do

    erb :index

    end

    get '/admin' do

    erb :admin

    end

    Chcielibyśmy oznaczyć datownikiem każde powiadomienie, więc oto bardzo prosta definicja funkcji:

    def timestamp

    Time.now.strftime ("% H:% M:% S")

    end

    Chcemy również ustawić dwie puste tablice: jedną do przechowywania połączeń, a drugą do przechowywania naszych powiadomień.

    connections = []

    notifications = []

    Teraz dla tras: kiedy nasza przeglądarka ładuje swoją stronę, mamy uruchomiony JavaScript, który użyje obiektu EventSource do połączenia się z adresem URL pod adresem http: // localhost: 4567 / connect. (Więcej o EventSource później.) Na razie możesz zobaczyć magię strumienia HTTP o zdarzeniu. Połączenie pozostaje otwarte do momentu wywołania zwrotnego w celu zamknięcia strumienia.

    get '/connect', provides: 'text/event-stream' do

    stream :keep_open do |out|

    connections << out

    #out.callback on stream close evt.

    out.callback {

    #delete the connection

    connections.delete(out)

    }

    end

    end

    Na koniec wszelkie dane, które są publikowane na trasie / push, są wypychane do każdego podłączonego urządzenia:

    post '/push' do

    puts params

    #Add the timestamp to the notification

    notification = params.merge( {'timestamp' => timestamp}).to_json

    notifications << notification

    notifications.shift if notifications.length > 10

    connections.each { |out| out << "data: #{notification}\n\n"}

    end

    Jak powiedziałem wcześniej, możesz po prostu postępować zgodnie z instrukcjami w repozytorium Git, aby ściągnąć i zbudować ten kod. Lub, jeśli śledziłeś, uruchom Terminal, przejdź do katalogu, w którym znajduje się twój kod, i wykonaj następujące czynności:

    $ ruby stream.rb

    W porządku, teraz mamy uruchomioną aplikację Sinatra z niestandardowymi trasami do obsługi żądań przychodzących z naszej przeglądarki. Jeśli nie ma to jeszcze pełnego sensu, po prostu się rozluźnij. W kolejnych podrozdziałach reszta elementów zacznie się układać.

    Konfigurowanie stron HTML

    Będziemy budować dwie strony: jedną dla administratora, aby wysyłać powiadomienia, a drugą dla podłączonych odbiorników, aby otrzymywać powiadomienia. Oba te "widoki" będą miały ten sam układ, jak pokazano tutaj:

    < html >

    < head >

    < title >HTML5 Hacks - Server Sent Events< /title >

    < meta charset="utf-8" / > < script src=http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js >

    < /script >

    < script src=http://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.js >

    < /script >

    < script src="jquery.notify.js" type="text/javascript" >< /script >

    < link rel="stylesheet" type="text/css" href="style.css" >

    < link rel="stylesheet" type="text/css" href="ui.notify.css" >

    < /head >

    < body >

    < !-- implementaion specific here -- >

    < /body >

    < /html >

    Strona administratora będzie zawierała tag < input > i prosty przycisk:

    < div id="wrapper" >

    < input type="text" id="message" placeholder=" Enter Notification Here" / >

    < input type="button" id="send" data-role="button" >push< /input >

    < /div >

    Na naszych stronach odbiorczych będzie wyświetlany prosty fragment tekstu:

    < div id="wrapper" >

    < p >Don't Mind me ... Just Waiting for a Push Notification from HTML5 Hacks.< /p >

    < /div >

    Uruchamiając jedno okno przeglądarki na http: // localhost: 4567 / admin, powinniśmy teraz zobaczyć nasz formularz administratora.

    Dodanie trochę jQuery

    Musimy dodać trochę JavaScript, aby dołączyć detektor zdarzeń do przycisku wysyłania. Ten fragment kodu uniemożliwi domyślne przesłanie formularza i opublikuje obiekt powiadomienia na serwerze w formacie JSON. Zwróć uwagę na mapy URL / push do trasy, którą zdefiniowaliśmy w naszej aplikacji Sinatra:

    $('#send').click(function(event) {

    event.preventDefault();

    var notification = { notification: $('#notification').val()};

    $.post( '/push', notification,'json');

    })

    Teraz otwórzmy pięć okien przeglądarki: jednego administratora pod adresem http: // localhost: 4567 / admin i czterech kolejnych odbiorników pod adresem http: // localhost: 4567. Wygląda dobrze. Nadszedł czas, aby skonfigurować nasze źródło zdarzeń.

    EventSource

    EventSource API to bardzo prosty interfejs API JavaScript do otwierania pliku przy połączeniu ze strumieniem HTTP. Ponieważ nasze strony odbiorcze to po prostu "głupie" terminale odbierające dane, mamy idealny scenariusz dla zdarzeń po stronie serwera. Jeśli chcesz, aby komunikacja dwukierunkowa miała miejsce, w tym rozdziale znajduje się wiele przykładów protokołu WebSocket. Wcześniej, kiedy omawialiśmy aplikację Sinatra, widzieliście, jak ujawnić trasę dla przeglądarki, aby połączyć się ze strumieniem HTTP. Cóż, tutaj się łączymy!

    var es = new EventSource('/connect');

    es.onmessage = function(e) {

    var msg = $.parseJSON(event.data);

    // ... Notify

    }

    Teraz możemy dodać proste powiadomienie z dostępnymi danymi:

    var es = new EventSource('/connect');

    es.onmessage = function(e) {

    var msg = $.parseJSON(event.data);

    // ... Notify

    }

    A oto ostateczny skrypt dla administratora:

    $(function() {

    $('#send').click(function(event) {

    event.preventDefault();

    var notification = {message: $('#notification').val()};

    $.post( '/push', notification,'json');

    })

    });

    Instalowanie jQuery.notify

    Do naszych powiadomień push będziemy używać świetnej wtyczki jQuery Erica Hyndsa, jQuerynotify. Aby wyświetlić powiadomienie, musimy umieścić na stronie odbiorcy pewne znaczniki:

    < div id = "container" style = "display: none" >

    < div id = "basic-template" >

    < a class="ui-notify-cross ui-notify-close" href="#" > x < /a >

    < h1 > # {title} < /h1 >

    < p > # {text} < /p >

    < /div >

    < /div >

    Spowoduje to utworzenie ukrytego znacznika DIV na dole dokumentu. Nie pokazujemy CSS, który używa "display: none", aby go ukryć, ale możesz zobaczyć więcej, badając kod źródłowy w towarzyszącym repozytorium Git. Aby jQuery.notify się zainicjował, musimy najpierw wywołać następujące czynności:

    $("#container").notify({

    speed: 500,

    expires: false

    });

    Oto ostateczny skrypt dla odbiorcy:

    $(function() {

    $("#container").notify({

    speed: 500,

    expires: false

    });

    var es = new EventSource('/connect');

    es.onmessage = function(e) {

    var msg = $.parseJSON(event.data);

    $("#container").notify("create", {

    title: msg.timestamp,

    text: msg.notification

    });

    }

    })

    To takie proste. Interfejs API EventSource jest minimalny, a podłączenie go do struktury internetowej, takiej jak Sinatra lub Node.js, jest proste. Teraz, gdy wysyłamy powiadomienia ze strony administratora, nasze strony odbiorców są aktualizowane powiadomieniami ze znacznikami czasu





    []